基本概念
- CI持续构建
- 之前的构建部署方式: npm run build(打包生成dist成品) scp上传到服务器 启动nginx服务
- 持续构建:
- gitlab进行代码管理 git push代码提pr
- 开启pr之后 进行一系列的检查(代码风格检查 unit测试 人工评审代码)合并代码
- git平台的webhook触发构建平台jenkins 负责拉取代码库中的代码(gitlab)
- 执行用户自定义好的shell脚本构建出制品(可以是压缩包夜可以是docker镜像) 然后将制品推送到制品库(nexus)
- 常见工具有 gitlab jenkins nexus nginx 这个环节只构建代码
- CD持续部署和持续交付
- 持续交付: 在完成构建之后 一般有两种选择
- 立即将制品部署到测试环境中
- 在规定的时间进行部署 不印象测试环境的使用
- 持续部署
- 使用自动化部署将构建的稳定包自动部署到生成环境
- 这样一个完整的代码开发生命周期就完成
- 持续部署工具
- Ansible批量部署
- docker拉推镜像
- Kubernetes Kubernetes(k8s)
- 在k8s中使用集群在组织服务器 集群中有一个master节点和若干个node节点
- master节点是集群的控制节点 负责调度集群中其他服务器的资源
- 10分钟看懂Docker和K8S
- 开发发布基本流程
- 编写完代码 提交到gitlab 开启pr
- 合并pr通过webhook或者手动启动jenkins的构建流程
- jenkins启动构建流程 执行配置好的shell脚本
- docker build生成docker镜像 将镜像上传到私有镜像库
- 使用kubectl指定远程的k8s集群 发送镜像版本更新指令
- 远程k8s集群收到指令 去镜像库拉取新镜像
- 镜像拉取成功 按升级策略(滚动发布)进行升级(A/B test 健康检查)
- 升级完成
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
- 基础安装
yum install vim wget ntpdate -y
- 关闭防火墙 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)
- 设置阿里云地址安装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
- 启动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
- 安装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
- 安装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
- raw.githubusercontent.com 无法访问或连接超时
1. 访问https://githubusercontent.com.ipaddress.com/raw.githubusercontent.com获取ip
2. vim /etc/hosts 199.232.96.133 raw.githubusercontent.com
- 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
- 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
- master节点查看启动情况
// 加入节点前和加入节点之后
kubectl get nodes
3. jenkins
- 安装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
- 初始化
// 访问 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
- 测试jenkins
// 新建任务 执行shell 提示无权限
docker -v
docker pull node:latest
- 权限问题
// 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
- 安装
// 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
- 初始化
// 访问 120.79.175.88:8081
// 获取初始密码 61258de4-e725-408c-9041-93428bd9f36e 默认的用户名为admin修改密码为admin
cat /root/sonatype-work/nexus3/admin.password
- 创建一个docker私服
// 1. create Repositories 选择docker (hosted)
// 2. 给镜像库添加访问权限 Security Realms Docker Bearer Token Realm Active
- 登录
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构建镜像发布
- 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 .
- 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
- 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. 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.部署之前需要声明一份配置清单 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
- 创建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
- 访问页面
// 查看对应的服务和端口
kubectl get svc
- 使用使用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
- 使用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
- 灰度发布 A/B测试
8.部署前后端分离项目
- 部署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对应的端口
// 创建一个user表
SET 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;
- 部署前端项目
// 之前是一个简单的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
- 后端项目部署
// 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
- 访问项目
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 流程 墙裂推荐