一、云原生

云原生是面向云应用设计的一种全新架构理念,是充分发挥云效能的最佳实践路径,可以帮助企业构建弹性可靠、松耦合、易管理可观测的应用系统,提升关键应用的交付效率,降低系统的运维复杂度。

云原生是一种文化,更是一种潮流,也是云计算的一个必然导向。意义在于让云成为云化战略成功的基石,而不是障碍。

二、k8s部署助力项目上云

在完成整体代码的开发后 , 我们还需要考虑部署问题 。

这个时候k8s就有了很大的发挥空间,使用 Docker 接入整个开发 、 生产 、 打包流程 , 保证各运行环境一致、使用 Kubernetes 作为容器编排 ,最终解决项目部署云端的问题。

1、主要思路

  • 本地开发时使用 Docker 开发
  • 推送代码至 Gitlab 触发 CI
  • CI 基于基础镜像打包 , 每个 COMMIT ID 对应一个镜像 , 推送至私有仓库 ,触发 CD
  • CD 通过 kubectl 控制 K8s 集群更新应用

2、在本地开发阶段 , 我们将依赖下载及开发模式分开 。

# 依赖下载
docker run -it \
    -v $(pwd)/package.json:/opt/work/package.json \
    -v $(pwd)/yarn.lock:/opt/work/yarn.lock \
    -v $(pwd)/.yarnrc:/opt/work/.yarnrc \ 
    # 挂载 package.json 、 yarn.lock 、 .yarnrc 到 /opt/work/ 下
    -v mobile_node_modules:/opt/work/node_modules \ 
    # /opt/work/node_modules 挂载为 mobile_node_modules 数据卷
    --workdir /opt/work \
    --rm node:13-alpine \
    yarn

在依赖下载中 , 思路是将 node_modules 目录作为一个数据卷 。 在需要使用时将其挂载到指定目录下 , 之后只需要将会影响到依赖下来的相关文件挂载到容器中 , 将 node_modules 数据卷挂载到文件夹 。 这样子就能持久化存储依赖文件 。

# 开发模式
docker run -it \
    -v $(pwd)/:/opt/work/ \ 
    # 挂载项目目录至 / opt/work/ 下
    -v mobile_node_modules:/opt/work/node_modules \ 
    # 挂载 node_modules 数据卷到 /opt/work/node_modules 目录下
    --expose 8081 -p 8081:8081 \ # HotReload Socket
    --expose 9229 -p 9229:9229 \ # debugger
    --expose 3003 -p 3003:3003 \ # Node Server
    # 暴露各个端口
    --workdir /opt/work \
    node:13-alpine \
    ./node_modules/.bin/nodemon --inspect=0.0.0.0:9229 --watch server server/bin/www

开发模式下 , 我们只需要将之前的 node_modules 数据卷挂载到 node_modules 目录 , 再将项目目录挂载到容器中 。 暴露指定端口即可开始开发 。 这里 8081 为写死的 HotReload Socket 接口 、 3003 为 Node 服务接口 、 9229 为 debugger 接口 。 再把启动命令设置为开发模式指令就可以正常开发 。

3、推送代码 , 触发 CI 。

在 CI 阶段 , 我们通过 Dockerfile 为每一次提交记录都生成一个与之对应的镜像 。

FROM node:13-alpine

COPY package.json /opt/dependencies/package.json
COPY yarn.lock /opt/dependencies/yarn.lock
COPY .yarnrc /opt/dependencies/.yarnrc
RUN cd /opt/dependencies \
    && yarn install --frozen-lockfile \
    && yarn cache clean \
    && mkdir /opt/work \
    && ln -s /opt/dependencies/node_modules /opt/work/node_modules

# 具体文件处理
COPY ci/docker/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
COPY ./ /opt/work/
RUN cd /opt/work \
    && yarn build

WORKDIR /opt/work
EXPOSE 3003
ENV NODE_ENV production

ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["node", "server/bin/www"]

上面是我们使用到的一个 Dockerfile 。

  1. 使用 node:13-alpine 作为基础镜像
  2. 复制依赖相关文件到容器中下载依赖 , node_modules 软连接到 /opt/work 下 。 清理安装缓存
  3. 复制项目文件到容器中 , 执行客户端代码打包命令
  4. 设置环境变量 , 对外暴露服务端口 , 设置镜像启动命令
docker build -f Dockerfile --tag frontend-mobile:COMMIT_SHA .

最后使用以上命令将该版本打包为一个镜像 , 推送至私有仓库 。

我们在 Dockerfile 优化编译速度及镜像体积时使用到的一些技巧:

  1. 前置合并不变的操作 , 将下载依赖和编译分开为两个RUN 指令 , 可以利用 Docker 的层缓存机制 。 在依赖不变的情况下 , 跳过依赖下载部分 , 直接使用之前的缓存。
  2. 每次操作后清理不需要的文件 , 如 yarn 生成的全局缓存 ,这些缓存不会影响到我们程序的运行 。 还有很多包管理工具也会生成一些缓存 , 按各种需要清理即可 。
  3. ‘.dockerignore’ 中忽略不影响到编译结果的文件 , 下次这些文件变动时 , 打包会直接使用之前的镜像 , 改个 README 或者一些 K8s 发布配置时就不会重新打包镜像 。

4、推送镜像至私有仓库 , 触发 CD 。

使用 Kubernetes 进行容器编排 。

K8s 非常的灵活且智能 。 我们只需要描述我们需要怎么样的应用程序 。 K8s 就会根据资源需求和其他约束自动放置容器 。括一些自动水平扩展 , 自我修复 。能方便我们去追踪监视每个应用程序运行状况 。

CD 容器通过 kubectl 控制 K8s 集群 。在每个分支提交代码触发 CD 之后 , 会为每个分支单独创建一个 Deployment 。 对应每个分支环境 。通过 Service 暴露一组指定 Deployment 对应的 Pod 服务 , Pod 运行的是 Deployment 指定的应用镜像 。最后使用 Ingress 根据域名区分环境对外提供服务 。

5、k8s配置

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-mobile  # deployment 名称
  namespace: mobile # 命名空间
  labels:
    app: frontend-mobile # 标签
spec:
  selector:
    matchLabels:
     # 对应的 Pod 标签, 被其选择的 Pod 的现有副本集将受到此部署的影响
     app: frontend-mobile
  replicas: 8 # Pod 节点数量, 默认为 1
  template:   # 相当于 Pod 的配置
    metadata:
      name: frontend-mobile  # Pod 名称
      labels:
        app: frontend-mobile # Pod 标签
    spec:
      containers:
        - name: frontend-mobile
          image: nginx:latest
          ports:
            - containerPort: 3003
          resources: # 设置资源限制
            requests:
              memory: "256Mi"
              cpu: "250m"     # 0.25 个cpu
            limits:
              memory: "512Mi"
              cpu: "500m"     # 0.5 个cpu
          livenessProbe:
            httpGet:
              path: /api/serverCheck
              port: 3003
              httpHeaders:
                - name: X-Kubernetes-Health
                  value: health
            initialDelaySeconds: 15
            timeoutSeconds: 1
---
apiVersion: v1
kind: Service
metadata:
  name: frontend-mobile  # Service 名称
  namespace: mobile # 命名空间
  labels:
    app: frontend-mobile # 标签
spec:
  selector:
    app: frontend-mobile # 对应的 Pod 标签
  ports:
    - protocol: TCP
      port: 8081       # 服务端口
      targetPort: 3003 # 代理端口
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: frontend-mobile
  namespace: mobile # 命名空间
  labels:
    app: frontend-mobile # 标签
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: local-deploy.com
      http:
        paths:
          - path: /
            backend:
              serviceName: frontend-mobile # 引用的服务名称
              servicePort: 8081         # 引用的服务端口, 对应 Service 中的 port

三、总结

到这里,前端项目的部署在k8s上的配置基本ok了。

CI / CD管道通过一系列测试套件和部署环境来促进变化。 通过一个阶段需求的更改会自动部署或排队,以便手动部署到更严格的环境中。 早期阶段是为了证明继续测试并推动变更更接近生产是值得的。

尤其对于后期阶段,在测试环境中尽可能接近地再现生产环境有助于确保测试准确地反映变化在生产中的表现。 分期和生产之间的显着差异可以允许发布有问题的变更,而这些变更在测试中从未被发现过。 

每个CI / CD的实施将有所不同,遵循这些基本原则中的一些将帮助您避免一些常见的陷阱并加强您的测试和开发实践。 与持续集成的大多数方面一样,过程,工具和习惯的混合将有助于使发展变化更加成功和有影响力。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐