前端基建-CICD部署前后端分离项目

2,137 阅读4分钟

基本概念

  1. CI持续构建
  • 之前的构建部署方式: npm run build(打包生成dist成品) scp上传到服务器 启动nginx服务
  • 持续构建:
    1. gitlab进行代码管理 git push代码提pr
    2. 开启pr之后 进行一系列的检查(代码风格检查 unit测试 人工评审代码)合并代码
    3. git平台的webhook触发构建平台jenkins 负责拉取代码库中的代码(gitlab)
    4. 执行用户自定义好的shell脚本构建出制品(可以是压缩包夜可以是docker镜像) 然后将制品推送到制品库(nexus)
  • 常见工具有 gitlab jenkins nexus nginx 这个环节只构建代码
  1. CD持续部署和持续交付
  • 持续交付: 在完成构建之后 一般有两种选择
    1. 立即将制品部署到测试环境中
    2. 在规定的时间进行部署 不印象测试环境的使用
  • 持续部署
    • 使用自动化部署将构建的稳定包自动部署到生成环境
    • 这样一个完整的代码开发生命周期就完成
  • 持续部署工具
    1. Ansible批量部署
    2. docker拉推镜像
    3. Kubernetes Kubernetes(k8s)
      • 在k8s中使用集群在组织服务器 集群中有一个master节点和若干个node节点
      • master节点是集群的控制节点 负责调度集群中其他服务器的资源
      • 10分钟看懂Docker和K8S
  1. 开发发布基本流程
    1. 编写完代码 提交到gitlab 开启pr
    2. 合并pr通过webhook或者手动启动jenkins的构建流程
    3. jenkins启动构建流程 执行配置好的shell脚本
    4. docker build生成docker镜像 将镜像上传到私有镜像库
    5. 使用kubectl指定远程的k8s集群 发送镜像版本更新指令
    6. 远程k8s集群收到指令 去镜像库拉取新镜像
    7. 镜像拉取成功 按升级策略(滚动发布)进行升级(A/B test 健康检查)
    8. 升级完成

1. 云服务器

ssh root@120.79.175.88 master节点
ssh root@120.78.165.128 node1节点
ssh root@8.129.182.98 node2节点(设置污点用来部署mysql)

2. 部署k8s

  1. 基础安装
yum install vim wget ntpdate -y
  1. 关闭防火墙 swap分区 关闭Selinux
// 为什么要关闭 https://www.zhihu.com/question/374752553
systemctl stop firewalld & systemctl disable firewalld
swapoff -a (vi /etc/fstab)
setenforce 0 (vi /etc/sysconfig/selinux SELINUX=disabled)
  1. 设置阿里云地址安装docker
yum install -y yum-utils device-mapper-persistent-data lvm2
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum install docker-ce -y
  1. 启动docker 设置镜像源地址
systemctl start docker
systemctl enable docker
// 更换docker镜像源地址  https://cr.console.aliyun.com/cn-shenzhen/instances/mirrors
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://82u211y8.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
  1. 安装k8s组件
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
        http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
// kubelet是核心组件运行在所有集群的节点上 创建启动服务容器 
// kubectl 命令行工具 管理删除资源  kubeadm初始化集群 子节点加入
yum install -y kubelet kubeadm kubectl
systemctl enable kubelet && systemctl start kubelet
  1. 安装Flannel
// 配置pod子网络 通过创建一个虚拟网络让不同节点下服务有全局唯一的ip地址 可相互访问和链接
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
docker pull quay.io/coreos/flannel:v0.13.0-rc2
// 加载服务
kubectl apply -f kube-flannel.yml
  1. raw.githubusercontent.com 无法访问或连接超时
1. 访问https://githubusercontent.com.ipaddress.com/raw.githubusercontent.com获取ip 
2. vim /etc/hosts  199.232.96.133 raw.githubusercontent.com
  1. master节点
ssh root@120.79.175.88
hostnamectl set-hostname  master
vim /etc/hosts 120.79.175.88 master
// 配置初始化文件
kubeadm config print init-defaults > init-kubeadm.conf
vim init-kubeadm.conf
// 更换镜像仓库为阿里云地址 替换ip为master节点ip 配置pod网络为flannel网段

// 拉取默认的镜像
kubeadm config images pull --config init-kubeadm.conf
初始化k8s
kubeadm init --config init-kubeadm.conf

// 在修改配置的时候我将ip设置为了公网ip导致init出错 根据提示执行命令 发现挂了两个容器
docker ps -a | grep kube | grep -v pause 
// 查看详情
docker logs k8s_kube-apiserver_kube-apiserver-master_kube-system_3e98cd250e7b108f42e1a053b41e54be_6i 
// 修改配置文件的ip为内网ip地址
kubeadm reset
rm -rf /etc/kubernetes/manifests
// 重新初始化 成功安装提示执行命令
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

  1. node节点
hostnamectl set-hostname node1
hostnamectl set-hostname node2
// 在master节点拷贝配置文件
scp $HOME/.kube/config root@120.78.165.128:~/
scp $HOME/.kube/config root@8.129.182.98:~/
mkdir -p $HOME/.kube
sudo mv $HOME/config $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
// 加入master节点 可以重新生成
kubeadm token create --print-join-command

  1. master节点查看启动情况
// 加入节点前和加入节点之后
kubectl get nodes

3. jenkins

  1. 安装jenkins
yum install -y java
sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
yum install jenkins
// 启动jenkins
service jenkins start (service jenkins restart restart)
// 防火墙
firewall-cmd --zone=public --add-port=8080/tcp --permanent
firewall-cmd --zone=public --add-port=50000/tcp --permanent
systemctl reload firewalld
  1. 初始化
// 访问 120.79.175.88:8080
// 获取初始密码 a6a5bf2c70c24726bdc05a015edea4cb
cat /var/lib/jenkins/secrets/initialAdminPassword
// 先更换插件地址再下载插件
sed -i 's/http:\/\/updates.jenkins-ci.org\/download/https:\/\/mirrors.tuna.tsinghua.edu.cn\/jenkins/g' /var/lib/jenkins/updates/default.json && sed -i 's/http:\/\/www.google.com/https:\/\/www.baidu.com/g' /var/lib/jenkins/updates/default.json
// 下载推荐插件 创建用户 admin admin
  1. 测试jenkins
// 新建任务 执行shell 提示无权限
docker -v
docker pull node:latest

  1. 权限问题
// docker提供了一个用户组的概念 需要将执行shell用户添加到docker的用户组
sudo groupadd docker          #新增docker用户组
sudo gpasswd -a jenkins docker  #将当前用户添加至docker用户组
newgrp docker                 #更新docker用户组
sudo service jenkins restart
// 再次构建 成功

4. gitlab

// 搭建gitlab todo 暂时使用gitee代替gitlab

5. nexus

  1. 安装
// jenkins(构建)一般和master节点(部署)是分开的 nexus mysql gitlab也是分开的 暂时先都安装在master节点上
ssh root@120.79.175.88
wget https://dependency-fe.oss-cn-beijing.aliyuncs.com/nexus-3.29.0-02-unix.tar.gz
tar -zxvf ./nexus-3.29.0-02-unix.tar.gz
cd nexus-3.29.0-02/bin
// 启动服务
./nexus start

// 防火墙设置
firewall-cmd --zone=public --add-port=8081/tcp --permanent
firewall-cmd --zone=public --add-port=8082/tcp --permanent
  1. 初始化
// 访问 120.79.175.88:8081 
// 获取初始密码 61258de4-e725-408c-9041-93428bd9f36e 默认的用户名为admin修改密码为admin
cat /root/sonatype-work/nexus3/admin.password
  1. 创建一个docker私服
// 1. create Repositories 选择docker (hosted)
// 2. 给镜像库添加访问权限 Security Realms Docker Bearer Token Realm Active
  1. 登录
docker login 120.79.175.88:8082
// http需要配置insecure地址 https不需要
vi /etc/docker/daemon.json
{
  "insecure-registries" : [
    "120.79.175.88:8082"
  ],
}
// 重启docker
systemctl restart docker

6. jenkins + gitee + nexus构建镜像发布

  1. jenksin
1. 安装nodejs环境 使用 SSH 协议集成 Git 仓库源 这里使用gitee代替gitlab
2. 生成秘钥对 ssh-keygen -t rsa -C "907478372@qq.com"
3. cat /root/.ssh/id_rsa.pub在gitee中配置
4. cat /root/.ssh/id_rsa私钥添加凭证
5. 执行shell脚本 构建成功
npm install --registry=https://registry.npm.taobao.org
npm run build
docker build -t jenkins-test .

  1. gitee
仓库地址 git@gitee.com:liuxs_dream/k8s-demo-frontend.git
1. 配置安全设置 ssh公钥 
2. 使用dockerfile
FROM nginx
COPY dist /etc/nginx/html
WORKDIR /etc/nginx/html

  1. nexus将镜像上传到私有镜像库
在jenkins中修改shell命令
npm install --registry=https://registry.npm.taobao.org
npm run build
docker build -t 120.79.175.88:8082/jenkins-test .
docker push 120.79.175.88:8082/jenkins-test
// 提示没有权限

docker login -u "用户名" -p "密码" 120.79.175.88:8082
// 使用凭证代替
docker login -u $DOCKER_LOGIN_USERNAME -p $DOCKER_LOGIN_PASSWORD 120.79.175.88:8082
// 重新构建成功 查看nexus界面可以看到刚上传的镜像

7. 使用k8s部署第一个应用

  1. 基本概念
1. CI持续构建 之前的构建部署方式: npm run build(打包生成dist成品) scp上传到服务器 启动nginx服务 http访问
    1. gitlab进行代码管理 git push代码提pr
    2. 开启pr之后 进行一系列的检查(代码风格检查 unit测试 人工评审代码)合并代码
    3. git平台的webhook触发构建平台jenkins 负责拉取代码库中的代码(gitlab)
    4. 执行用户自定义好的shell脚本构建出制品(可以是压缩包夜可以是docker镜像) 然后将制品推送到制品库(nexus)
    常见工具gitlab jenkins nexus nginx 这个环节只构建代码
2. CD持续部署和持续交付
   持续交付: 在完成构建之后 一般有两种选择
     1. 立即将制品部署到测试环境中
     2. 在规定的时间进行部署 不印象测试环境的使用
   持续部署: 使用自动化部署将构建的稳定包自动部署到生成环境 这样一个完整的代码开发生命周期就完成
   工具:
     1. Ansible批量部署
     2. docker拉推镜像
     3. Kubernetes
3. Kubernetes(k8s)
    在k8s中使用集群在组织服务器 集群中有一个master节点和若干个node节点
    master节点是集群的控制节点 负责调度集群中其他服务器的资源
    [10分钟看懂Docker和K8S](https://zhuanlan.zhihu.com/p/53260098)
4. 基本流程
  1. 编写完代码 提交到gitlab 开启pr
  2. 合并pr通过webhook或者手动启动jenkins的构建流程
  3. jenkins启动构建流程 执行配置好的shell脚本
  4. docker build生成docker镜像 将镜像上传到私有镜像库
  5. 使用kubectl指定远程的k8s集群 发送镜像版本更新指令
  6. 远程k8s集群收到指令 去镜像库拉取新镜像
  7. 镜像拉取成功 按升级策略(滚动发布)进行升级(A/B test 健康检查)
  8. 升级完成
  1. 部署
// 1.部署之前需要声明一份配置清单 yaml格式 (应用部署都是通过yaml配置清单进行部署的)
mkdir deployment && cd deployment
vim v1.yaml
// 特别注意格式 缩进
# api配置版本
apiVersion: apps/v1
# 资源类型
kind: Deployment
metadata:
  # 资源名称
  name: front-v1
spec:
  selector:
    matchLabels:
      app: nginx-v1
  # 要创建的pod嘴阀数量
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx-v1
    # 组内创建的pod信息
    spec:
      containers:
      - name: nginx
        image: 120.79.175.88:8082/jenkins-test
        ports:
        # 映射的端口
        - containerPort: 80
// 2. 启动第一个应用
kubectl apply -f ./v1.yaml
// 3.查看pod的运行情况  deployment是无状态的不会对pod网络通信和分发使用Service组织统一的
kubectl get pod
// 4. 状态为ImagePullBackOff 查看详细发现 no basic auth credentials
kubectl describe pod front-v1-5c884cffb9-ntmgb

  1. 创建secret
// 使用secret存储机密信息(k8s的一种资源类型 存放一些机密信息 密码token 秘钥等)
// 创建一般都两种类型 
// 1.opaque类型一般用来存放密码 秘钥
kubectl create secret generic default-auth 
    --from-literal=username=admin \
    --from-literal=password=admin
// 2. 私有镜像库认证  kubectl get secret private-registry -o yaml可以查看
kubectl create secret docker-registry private-registry \
  --docker-username=admin \
  --docker-password=admin \
  --docker-email=907478372@qq.com \
  --docker-server=120.79.175.88:8082
  
//  使用
// Volume 挂载 设置secret 环境变量注入 env[].name
// Docker 私有库认证 修改v1配置文件
imagePullSecrets:
    - name: private-registry-file
// 重新kubectl apply -f ./v1.yaml

  1. 访问页面
// 查看对应的服务和端口 
kubectl get svc 

  1. 使用使用service解决无状态问题
// 编辑v1
apiVersion: v1
kind: Service
metadata:
  name: front-service-v1
spec:
  selector:
    app: nginx-v1
  ports:
  # 通信类型
  - protocol: TCP
    # 端口
    port: 80
    targetPort: 80
  # nodeport service的一种访问模式
  type: NodePort
kubectl apply -f ./v1.yaml
  1. 使用ingress来实现负载均衡
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/baremetal/deploy.yaml
vim deploy.yaml
// 添加nodePort 修改源地址 registry.cn-hangzhou.aliyuncs.com/bin_x/nginx-ingress:v0.34.1@sha256:80359bdf124d49264fabf136d2aecadac729b54f16618162194356d3c78ce2fe
kubectl apply -f deploy.yaml
自动拉取ingress镜像 自动部署ingress 查看状态
kubectl get pods -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx --watch
检查配置是否生效
kubectl -n ingress-nginx get svc

// 配置ingress
mkdir ingress && cd ingress && vim base.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx-demo
  # ingress的主要配置项目 修改配置来修改ingress的行为 可以实现灰度发布 跨域资源
  # https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: nginx
spec:
  # rules配置路径转发规则 
  rules:
  - http:
      paths: 
      # path可以是路径字符串或者正在表达式
      - path: /wss
        backend:
         serviceName: front-service-v1
         servicePort: 80
  # backend是service服务 serviceName是服务名称 port是端口
  # 当请求匹配不到rules中的规则就会走backend中的配置
  backend:
     serviceName: front-service-v1
     servicePort: 80
 kubectl apply -f ./base.yaml
  访问  http://120.79.175.88:31234/wss
      http://120.78.165.128:31234/wss

  1. 灰度发布 A/B测试

8.部署前后端分离项目

  1. 部署mysql
// 新增一个node节点 用来部署mysql
ssh root@8.129.182.98
设置污点只部署mysql 其他服务是不会部署到这个节点上
kubectl taint nodes node2 mysql=true:NoSchedule
// 创建mysql数据目录
mkdir /var/lib/mysql && mkdir /var/lib/mysql/data
// 使用secret保存密码
kubectl create secret generic demo-mysql-auth \
--from-literal=password=367734

// 部署master节点 一定要设置容忍度
vim mysql.YAML

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demo-mysql
  template:
    metadata:
      labels:
        app: demo-mysql
    spec:
      # 设置容忍度
      tolerations:
      - key: "mysql"
        operator: "Equal"
        value: "true"
        effect: "NoSchedule"
      containers:
      - name: demo-mysql
        image: mysql:5.6
        imagePullPolicy: IfNotPresent
        args:
        - "--ignore-db-dir=lost+found"
        ports:
        # 设置3306端口号
        - containerPort: 3306
        volumeMounts:
        - name: mysql-data
          mountPath: "/var/lib/mysql"
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: demo-mysql-auth
              key: password
      volumes:
      - name: mysql-data
        hostPath: 
          path: /var/lib/mysql
          type: Directory        
---
apiVersion: v1
kind: Service
metadata:
  name: demo-mysql-service
spec:
  type: NodePort
  ports:
  - port: 3306
    protocol: TCP
    targetPort: 3306
  selector:
    app: demo-mysql
kubectl apply -f mysql.YAML
// 测试mysql的链接 可以使用Navicat等工具链接 使用master的ip和svc3306对应的端口

// 创建一个userSET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

CREATE DATABASE IF NOT EXISTS `demo-backend` DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
USE `demo-backend`;

DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `name` varchar(255) NOT NULL COMMENT '姓名',
  `age` int(11) NOT NULL COMMENT '年龄',
  `sex` varchar(255) NOT NULL COMMENT '性别;1男 2女',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;

  1. 部署前端项目
// 之前是一个简单的vite初始化项目 先改为可以和服务器交互的项目
// 项目地址 git@gitee.com:liuxs_dream/k8s-demo-frontend.git
// jenkins新建任务 demo-frontend
// 1.执行shell脚本构建
  #!/bin/sh -l
  time=$(date "+%Y%m%d%H%M%S")
  npm install --registry=https://registry.npm.taobao.org
  npm run build
  docker build -t 120.79.175.88:8082/frontend-app:$time .
  docker login -u admin -p admin 120.79.175.88:8082
  docker push 120.79.175.88:8082/frontend-app:$time
// 2.构建生成镜像之后再k8s集群中部署这个镜像
vim demo-frontend.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-frontend
spec:
  selector:
    matchLabels:
      app: demo-frontend
  replicas: 1
  template:
    metadata:
      labels:
        app: demo-frontend
    spec:
      imagePullSecrets:
      - name: private-registry
      containers:
      - name: frontend-app
        imagePullPolicy: Always
        image: 120.79.175.88:8082/frontend-app:20210121150131
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: demo-frontend-service
spec:
  selector:
    app: demo-frontend
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: NodePort
kubectl apply -f demo-frontend.yaml
kubectl get svc
  1. 后端项目部署
// git@gitee.com:liuxs_dream/k8s-demo-backend.git
// 1.jenkins执行shell构建镜像
  #bin/bash
  time=$(date "+%Y%m%d%H%M%S")
  npm install --registry=https://registry.npm.taobao.org
  docker build -t 120.79.175.88:8082/backend-app:$time .
  docker login -u admin -p admin 120.79.175.88:8082
  docker push 120.79.175.88:8082/backend-app:$time
  
// 2.在k8s中部署 先要使用configMap(统一管理服务环境变量)保存数据库信息 secret加密 configMap的内容不会加密
vim mysql-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  host: 'demo-mysql-service'  
  port: '3306'
  username: 'root'
  database: 'demo-backend'
kubectl apply -f mysql-config.yaml

// 部署
vim demo-backend.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-backend
spec:
  selector:
    matchLabels:
      app: demo-backend
  replicas: 1
  template:
    metadata:
      labels:
        app: demo-backend
    spec:
      imagePullSecrets:
      - name: private-registry
      containers:
      - name: backend-app
        imagePullPolicy: Always
        image: 120.79.175.88:8082/frontend-app:20210121172141
        ports:
        - containerPort: 7001
        env:
        - name: MYSQL_HOST
          valueFrom:
            configMapKeyRef:
              name: mysql-config
              key: host
        - name: MYSQL_PORT
          valueFrom:
            configMapKeyRef:
              name: mysql-config
              key: port
        - name: MYSQL_USER
          valueFrom:
            configMapKeyRef:
              name: mysql-config
              key: username
        - name: MYSQL_DATABASE
          valueFrom:
            configMapKeyRef:
              name: mysql-config
              key: database
---
apiVersion: v1
kind: Service
metadata:
  name: demo-backend-service
spec:
  selector:
    app: demo-backend
  ports:
  - protocol: TCP
    port: 7001
    targetPort: 7001
  type: NodePort

kubectl apply -f demo-backend.yaml
kubectl get pod

  1. 访问项目

9. 集成 Jenkins 使用jenkins直接一键执行构建和部署

// 之前的操作构建和部署是分开的 在jenkins中构建在master节点中手动的去部署服务 
// 一般jenkins和k8s不在同一个机器上 需要在jenkins机器上安装kubectl
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg
        http://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
yum install -y kubectl

// 1. 在jenkins中 Manage Jenkins => Managed files 添加~/.kube/config配置文件的内容
// 2. 在任务配置中 构建环境 Provide Configuration files
// 3. 在ssh中使用 kubectl --kubeconfig 指定配置文件
kubectl --kubeconfig=k8s-config.yaml set image deployment/demo-frontend frontend-app=120.79.175.88:8082/frontend-app:$time
// 重新构建提示  deployment.apps/demo-frontend image updated

10.todo

pm2
webhook action
notify发布完成通知
灰度发布 A/B test 
滚动发布 健康检查 回滚
服务发现 ...

特别感谢

内容来源小册 从 0 到 1 实现一套 CI/CD 流程 墙裂推荐