09 基于 K8S 构建 Jenkins 微服务发布平台


发布流程设计

  1. 拉取代码
  2. 编译
  3. 把jar包打到镜像里
  4. 部署到k8s平台,写yaml文件
  5. 暴露你的应用

准备基础环境

  1. K8S(Ingress Controller,CoreDNS)
  2. Helm v3
  3. Gitlab
  4. Harbor 并启用Chart存储功能
  5. MySQL(微服务数据库)
  6. eureka 注册中心

K8S 环境

coredns

1
2
3
4
5
6
[root@k8s-master1 ~]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-6d8cfdd59d-k5wl9 1/1 Running 12 6d18h
kube-flannel-ds-amd64-2k5kz 1/1 Running 6 6d18h
kube-flannel-ds-amd64-gvs6b 1/1 Running 12 6d18h
kube-flannel-ds-amd64-hwglz 1/1 Running 13 6d18h

ingress

1
2
3
4
5
[root@k8s-master1 ~]# kubectl get pods -n ingress-nginx
NAME READY STATUS RESTARTS AGE
nginx-ingress-controller-2zvq5 1/1 Running 6 6d18h
nginx-ingress-controller-9hq5n 1/1 Running 13 6d18h
nginx-ingress-controller-k5vsm 1/1 Running 12 6d18h

PV自动供给 NFS

1
2
3
4
5
6
7
8
9
10
# NFS服务器,每个Node上安装nfs-utils包,用于mount挂载时用
[root@k8s-node2 ~]# yum install nfs-utils

[root@k8s-node2 ~]# vi /etc/exports
/ifs/kubernetes *(rw,no_root_squash)

[root@k8s-node2 ~]# mkdir -p /ifs/kubernetes
[root@k8s-node2 ~]# systemctl daemon-reload
[root@k8s-node2 ~]# systemctl start nfs
[root@k8s-node2 ~]# systemctl enable nfs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 由于K8S不支持NFS动态供给,还需要先安装上图中的nfs-client-provisioner插件
[root@k8s-master1 opt]# unzip nfs-client.zip

[root@k8s-master1 opt]# cd nfs-client

[root@k8s-master1 nfs-client]# ls -l
total 12
-rw-r--r-- 1 root root 225 Jan 6 14:48 class.yaml # StorageClass 声明使用哪种存储插件,它来对接存储,设置好 StorageClass 名称
-rw-r--r-- 1 root root 981 Jan 5 10:36 deployment.yaml # 该服务帮我们自动创建 pv
-rw-r--r-- 1 root root 1526 Jan 5 10:36 rbac.yaml # 动态创建pv插件需要连接apiserver ,所以需要授权

[root@k8s-master1 nfs-client]# vim deployment.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: nfs-client-provisioner
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: lizhenliang/nfs-client-provisioner:latest
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: fuseim.pri/ifs
- name: NFS_SERVER
value: 172.31.228.53
- name: NFS_PATH
value: /ifs/kubernetes
volumes:
- name: nfs-client-root
nfs:
server: 172.31.228.53
path: /ifs/kubernetes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 创建 pv 动态供给 

[root@k8s-master1 nfs-client]# kubectl apply -f class.yaml
[root@k8s-master1 nfs-client]# kubectl apply -f rbac.yaml
[root@k8s-master1 nfs-client]# kubectl apply -f deployment.yaml

[root@k8s-master1 nfs-client]# kubectl get sc
NAME PROVISIONER AGE
managed-nfs-storage fuseim.pri/ifs 47s

# 查看自动创建pv的pod,当申请资源的时候这个pod会自动去创建pv

[root@k8s-master1 nfs-client]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-769f87c8f6-7hn29 1/1 Running 0 51s

应用包管理器 Helm V3

1
2
3
[root@k8s-master1 opt]# wget https://get.helm.sh/helm-v3.0.0-linux-amd64.tar.gz
[root@k8s-master1 opt]# tar zxvf helm-v3.0.0-linux-amd64.tar.gz
[root@k8s-master1 opt]# mv linux-amd64/helm /usr/bin/
1
2
3
4
5
6
7
8
9
10
# 配置国内Chart仓库 
[root@k8s-master1 opt]# helm repo add stable http://mirror.azure.cn/kubernetes/charts
[root@k8s-master1 opt]# helm repo add aliyun https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts

[root@k8s-master1 opt]# helm repo list
NAME URL
stable http://mirror.azure.cn/kubernetes/charts
aliyun https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts

[root@k8s-master1 opt]# helm search repo mysql
1
2
3
4
5
6
7
8
# 安装 push 插件
helm plugin install https://github.com/chartmuseum/helm-push

# 如果网络下载不了,也可以直接解压课件里包:
tar zxvf helm-push_0.7.1_linux_amd64.tar.gz
mkdir -p /root/.local/share/helm/plugins/helm-push
chmod +x bin/*
mv bin plugin.yaml /root/.local/share/helm/plugins/helm-push

微服务数据库 MySQL

1
2
3
4
5
6
7
8
[root@k8s-master2]# yum -y install mariadb mariadb-server
[root@k8s-master2]# systemctl start mariadb
[root@k8s-master2]# systemctl enable mariadb
[root@k8s-master2]# mysql_secure_installation

# 授权访问用户
[root@k8s-master2]# mysql -uroot -p
MariaDB [(none)]> grant all on *.* to 'root'@'%' identified by '123456';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 将微服务数据库导入 
[root@k8s-master1 db]# scp *.sql root@172.17.70.252:/tmp

MariaDB [(none)]> create database tb_order CHARACTER SET utf8mb4;
MariaDB [(none)]> create database tb_stock CHARACTER SET utf8mb4;
MariaDB [(none)]> create database tb_product CHARACTER SET utf8mb4;

MariaDB [(none)]> use tb_order;
MariaDB [tb_order]> source /tmp/order.sql;

MariaDB [tb_order]> use tb_stock;
MariaDB [tb_stock]> source /tmp/stock.sql;

MariaDB [tb_stock]> use tb_product;
MariaDB [tb_product]> source /tmp/product.sql;

MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| tb_order |
| tb_product |
| tb_stock |
| test |
+--------------------+
7 rows in set (0.03 sec)

代码版本仓库 Gitlab

docker 部署 gitlab

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@k8s-master2 pinpoint-docker]# mkdir -p /opt/gitlab
[root@k8s-master2 pinpoint-docker]# cd /opt/gitlab
# 数据目录挂载在本机,之后备份该目录即可
docker run -d \
--name gitlab \
-p 8443:443 \
-p 9999:80 \
-p 9998:22 \
-v $PWD/config:/etc/gitlab \
-v $PWD/logs:/var/log/gitlab \
-v $PWD/data:/var/opt/gitlab \
-v /etc/localtime:/etc/localtime \
lizhenliang/gitlab-ce-zh:latest

# 稍微等待几分钟启动后
# 访问地址:http://IP:9999
# 初次会先设置管理员密码 ,然后登陆,默认管理员用户名root,密码就是刚设置的。

镜像仓库 Harbor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# 之前部署的步骤,按照实际IP填写 
# 该主机也需要二进制安装docker
[root@k8s-master2]# scp root@172.17.70.254:/opt/src/docker-18.09.6.tgz .
[root@k8s-master2]# scp root@172.17.70.254:/usr/lib/systemd/system/docker.service .
[root@k8s-master2]# scp root@172.17.70.254:/etc/docker/daemon.json .
[root@k8s-master2]# tar -zxvf docker-18.09.6.tgz
[root@k8s-master2]# mv docker/* /usr/bin/
[root@k8s-master2]# mv docker.service /usr/lib/systemd/system
[root@k8s-master2]# mkdir -p /etc/docker
[root@k8s-master2]# mv daemon.json /etc/docker/

[root@k8s-master2]# cat /etc/docker/daemon.json
{
"registry-mirrors": ["http://bc437cce.m.daocloud.io"], # 镜像加速
"insecure-registries": ["172.17.70.252"] # 可信任IP 回头换成harbor地址
}

[root@k8s-node1 src]# systemctl start docker
[root@k8s-node1 src]# systemctl enable docker
[root@k8s-node1 src]# docker info

# 上传文件
[root@k8s-master2 src]# ls -l
-rw-r--r-- 1 root root 17237024 Nov 13 14:33 docker-compose-Linux-x86_64
-rw-r--r-- 1 root root 580462944 Nov 13 14:34 harbor-offline-installer-v1.8.4.tgz

# 部署 compose
[root@k8s-master2 src]# mv docker-compose-Linux-x86_64 /usr/local/bin/docker-compose
[root@k8s-master2 src]# chmod +x /usr/local/bin/docker-compose
[root@k8s-master2 src]# docker-compose -version
docker-compose version 1.25.0dev, build bc57a1bd

# 部署 harbor
[root@k8s-master2 src]# tar -xf harbor-offline-installer-v1.8.4.tgz -C /opt/
[root@k8s-master2 src]# cd /opt/harbor/
# 修改主机名和管理员密码、数据库密码
hostname: 172.17.70.252 # http
harbor_admin_password: 123456 # 访问密码
database:
password: 123456

# 准备
[root@k8s-master2 src]# ./prepare
# 安装 --with-chartmuseum 参数表示启用Charts存储功能。
[root@k8s-master2 src]# ./install.sh --with-chartmuseum
# web访问
http://123.56.14.192
# 列出
docker-compose ps

# 配置Docker可信任
由于habor未配置https,还需要在docker配置可信
[root@Docker harbor]# vim /etc/docker/daemon.json
# 写入进项仓库 IP+port

{
"registry-mirrors": ["http://f1361db2.m.daocloud.io"],
"insecure-registries": ["172.17.70.252"]
}

# 重启docker
systemctl daemon-reload
systemctl restart docker.service

[root@Docker nginx]# docker info
Insecure Registries:
172.17.70.252
127.0.0.0/8

在 Kubernetes 中部署 Jenkins

1
2
参考:
https://github.com/jenkinsci/kubernetes-plugin/tree/fc40c869edfd9e3904a9a56b0f80c5a25e988fa1/src/main/kubernetes
  1. Jenkins 需要持久化存储
  2. K8S的pod不固定,所以需要持久化存储 pv,pvc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@k8s-master1 opt]# unzip jenkins.zip 
Archive: jenkins.zip
creating: jenkins/
inflating: jenkins/deployment.yml
inflating: jenkins/ingress.yml
inflating: jenkins/rbac.yml
inflating: jenkins/service-account.yml
inflating: jenkins/service.yml

[root@k8s-master1 opt]# cd jenkins

[root@k8s-master1 jenkins]# ls -l
-rw-r--r-- 1 root root 1953 Jan 5 10:36 deployment.yml
-rw-r--r-- 1 root root 349 Jan 5 10:36 ingress.yml
-rw-r--r-- 1 root root 908 Jan 5 10:36 rbac.yml # 授权 k8s-apiserver
-rw-r--r-- 1 root root 914 Jan 5 10:36 service-account.yml # 访问 k8s-apiserver 调度创建pod
-rw-r--r-- 1 root root 270 Jan 5 10:36 service.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
[root@k8s-master1 jenkins]# cat deployment.yml 
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins
labels:
name: jenkins
spec:
replicas: 1
selector:
matchLabels:
name: jenkins
template:
metadata:
name: jenkins
labels:
name: jenkins
spec:
terminationGracePeriodSeconds: 10
serviceAccountName: jenkins
containers:
- name: jenkins
# 官方长期维护版本
image: jenkins/jenkins:lts
imagePullPolicy: Always
ports:
# 8080 ui
# 50000 slave访问master端口
- containerPort: 8080
- containerPort: 50000
resources:
limits:
cpu: 1
memory: 1Gi
requests:
cpu: 0.5
memory: 500Mi
env:
- name: LIMITS_MEMORY
valueFrom:
resourceFieldRef:
resource: limits.memory
divisor: 1Mi
- name: JAVA_OPTS
value: -Xmx$(LIMITS_MEMORY)m -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
volumeMounts:
- name: jenkins-home
# 挂载点
mountPath: /var/jenkins_home
livenessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 5
failureThreshold: 12
readinessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 5
failureThreshold: 12
securityContext:
fsGroup: 1000
volumes:
# 数据卷
- name: jenkins-home
persistentVolumeClaim:
# pvc
claimName: jenkins-home
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
# 自动供给
name: jenkins-home
spec:
storageClassName: "managed-nfs-storage"
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@k8s-master1 jenkins]# cat service.yml 
apiVersion: v1
kind: Service
metadata:
name: jenkins
spec:
selector:
name: jenkins
type: NodePort
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
nodePort: 30006
- name: agent
port: 50000
protocol: TCP

[root@k8s-master1 jenkins]# cat ingress.yml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: jenkins
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: 100m
spec:
rules:
- host: jenkins.ctnrs.com
http:
paths:
- path: /
backend:
serviceName: jenkins
servicePort: 80
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 创建
[root@k8s-master1 jenkins]# kubectl apply -f .

[root@k8s-master1 jenkins]# kubectl get pods,svc,pv,pvc -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/jenkins-557cf6fd7f-nvwbp 1/1 Running 0 10m 10.244.1.38 k8s-node1 <none> <none>
pod/nfs-client-provisioner-769f87c8f6-7hn29 1/1 Running 0 99m 10.244.2.26 k8s-master1 <none> <none>

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/jenkins NodePort 10.0.0.47 <none> 80:30006/TCP,50000:31324/TCP 10m name=jenkins
service/kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 6d20h <none>

NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE VOLUMEMODE
persistentvolume/pvc-ffe668d2-1fd1-403d-87c6-739ccf6bd6ee 5Gi RWO Delete Bound default/jenkins-home managed-nfs-storage 10m Filesystem

NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE VOLUMEMODE
persistentvolumeclaim/jenkins-home Bound pvc-ffe668d2-1fd1-403d-87c6-739ccf6bd6ee 5Gi RWO managed-nfs-storage 10m Filesystem

# 查看日志
[root@k8s-master1 jenkins]# kubectl logs jenkins-557cf6fd7f-nvwbp -f

# 初始化密码 启动日志中查看到 0791683090e046489635805ade39f81c


# web访问
http://47.240.14.16:30006/
1
2
3
# 修改时区 
# 打开 【系统管理】->【脚本命令行】运行下面的命令
System.setProperty('org.apache.commons.jelly.tags.fmt.timeZone', 'Asia/Shanghai')

Jenkins Pipeline

Jenkins Pipeline 及参数化构建

  1. Jenkins Pipeline是一套插件,支持在Jenkins中实现集成和持续交付管道;
  2. Pipeline通过特定语法对简单到复杂的传输管道进行建模;
  3. 声明式:遵循与Groovy相同语法。pipeline { }
  4. 脚本式:支持Groovy大部分功能,也是非常表达和灵活的工具。node { }
  5. Jenkins Pipeline的定义被写入一个文本文件,称为Jenkinsfile。
  6. 参考
1
https://jenkins.io/doc/book/pipeline/syntax/

安装插件 Jenkins Pipeline

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 修改国内插件源
# 修改实际下载插件地址
# 在持久化目录中
[root@k8s-node2 kubernetes]# cd default-jenkins-home-pvc-ffe668d2-1fd1-403d-87c6-739ccf6bd6ee/
[root@k8s-node2 default-jenkins-home-pvc-ffe668d2-1fd1-403d-87c6-739ccf6bd6ee]# cd updates

# sed 批量替换
# 替换清华源下载插件地址
sed -i 's/http:\/\/updates.jenkins-ci.org\/download/https:\/\/mirrors.tuna.tsinghua.edu.cn\/jenkins/g' default.json
# 替换URL检查地址
sed -i 's/http:\/\/www.google.com/https:\/\/www.baidu.com/g' default.json

# 页面重启 jenkins 服务
http://47.240.14.16:30006/restart YES
1
2
3
4
5
# 安装插件 
http://47.240.14.16:30006/pluginManager/
# 选择可用插件 搜索 Pipeline
http://47.240.14.16:30006/pluginManager/available
# 安装插件完成后 创建job出现 流水线 为正常

创建测试 Pipeline

  1. Pipeline 的脚本语法为 Groovy
  2. 选择一个声明式的用例粘贴出来去修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 基础语法
# 1. stages: 大步骤,拉取代码 到编译、发布 每一步都为步骤
# 2. stage: 小步骤,卸载一个大步骤里
# 3. steps: 小步骤里面的小单元,具体任务的分节

pipeline {
agent any

stages {
stage('1. 拉取代码') {
steps {
echo 'Hello World'
}
}

stage('2. 代码编译') {
steps {
echo 'Hello World'
}
}

stage('3. 单元测试') {
steps {
echo 'Hello World'
}
}

}
}

参数化构建

1
2
3
4
5
6
7
# 多个项目部署, 套用一个pipeline,找出不同点
1. git地址
2. 分支名
3. 部署的机器
4. 打出的包名

# 知道不同点,那么可以使用参数化构建

流水线生成 git 项目地址

1
2
3
4
1. 测试生成 git项目地址 选择框
2. 复制代码到 pipeline
3. 保存后先构建一次,再进入项目会看到 构建+参数选项
4. 在这里就可以选择 发布项目的git地址
1
2
3
4
5
6
7
8
9
pipeline {
agent any
parameters {
choice choices: ['172.31.228.51:9999/root/a.git', '172.31.228.51:9999/root/b.git', '172.31.228.51:9999/root/c.git'], description: '请选择要发布的项目git地址', name: 'git'
}

stages {
...
}

流水线生成 分支名

1
2
# 动态参数化构建
# 分支名需要动态的从选择的 git地址里面 后去所有分支

流水线生成 部署的机器

1
2
3
4
5
6
7
pipeline {
agent any
parameters {
choice choices: ['172.31.228.51:9999/root/a.git', '172.31.228.51:9999/root/b.git', '172.31.228.51:9999/root/c.git'], description: '请选择要发布的项目git地址', name: 'git'
choice choices: ['172.31.228.50', '172.31.228.52', '172.31.228.53'], description: '请选择要发布到的服务器', name: 'host'
}
...

脚本中获取参数

1
2
# 通过设置的Name 就是变量名获取值
# pipeline 脚本中直接获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
pipeline {
agent any
parameters {
choice choices: ['172.31.228.51:9999/root/a.git', '172.31.228.51:9999/root/b.git', '172.31.228.51:9999/root/c.git'], description: '请选择要发布的项目git地址', name: 'git'
choice choices: ['172.31.228.50', '172.31.228.52', '172.31.228.53'], description: '请选择要发布到的服务器', name: 'host'
}

stages {
stage('1. 拉取代码') {
steps {
echo "${git}"
}
}

stage('2. 代码编译') {
steps {
echo 'Hello World'
}
}

stage('3. 单元测试') {
steps {
echo 'Hello World'
}
}

stage('4. 部署') {
steps {
echo "${host}"
}
}

}
}

Jenkins 在 Kubernetes 中动态创建代理

Jenkins Master/Slave架构

  1. 上面的例子中,所有的动态创建都是在Jenkins服务器中处理完成
  2. 我们的Jenkins部署在了k8s里,所以是pod里的Jenkins服务 处理的编辑构建等操作
  3. 如果我们的项目很多,每天都做持续继承的频率也很高,面对大批量的任务处理,1个pod是很难支持的
  4. 所以我们需要使用 Jenkins Master/Slave架构 , Master负责任务的分配,Slave完成job任务
  5. Slave 之前是作为一个虚拟机或者物理机存在,它与Master保持通信获取任务,解决 Jenkins 服务问题
1
2
3
4
[root@k8s-master1 jenkins]# kubectl get pods
NAME READY STATUS RESTARTS AGE
jenkins-6459665769-2dzp7 1/1 Running 0 97m
nfs-client-provisioner-769f87c8f6-sdk9q 1/1 Running 0 118m

架构说明

  1. 传统的Jenkins部署完成后, 再在其他的虚拟机上部署几个Jenkins 做为Slave ,在页面的管理节点中配置好
  2. 当点击job构建的时候 Master将任务分配给Slave去完成,Master 本身没有太大工作压力

  1. 当用k8s环境的时候,Slave也应该作为pod运行在k8s中,Master的任务交给pod中的Slave去完成
  2. 考虑到两种运行方式,预先启动和动态创建,预先创建也没有问题,但是会一直消耗资源
  3. 动态创建Slave,当执行任务的时候,master来组织创建Slave pod ,然后在把任务交给Slave去完成,完成后销毁pod 即开即用,节省资源

动态创建代理

  1. Kubernetes插件:Jenkins在Kubernetes集群中运行动态代理
1
插件介绍:https://github.com/jenkinsci/kubernetes-plugin

安装插件

  1. 如果想动态的在k8s中创建Slave pod 需要配置连接参数
  2. 连接的k8s地址
  3. 连接的Jenkins地址

配置插件

1
2
3
4
5
6
1. 系统配置最下面
2. 首先 Jenkins Master 是运行在k8s里面的,作为一个pod 他本身就可以访问到k8s下面的pod资源
3. 他连接k8s地址可以用svc名字访问,dns可以解析到
4. Kubernetes 地址: https://kubernetes.default
5. 如果 Jenkins 是部署在外面 需要连接k8s的外部地址 https://172.31.228.50:6443 并且需要添加CA证书和凭据
6. Jenkins 地址 :http://jenkins.default 同样适用svc地址 也可以使用当前页面的地址+端口
1
2
3
4
[root@k8s-master1 jenkins]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
jenkins NodePort 10.0.0.51 <none> 80:30006/TCP,50000:30523/TCP 144m
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 7d14h

自定义构建 Jenkins Slave 镜像

1
2
3
4
1. 现在 jenkins 已经知道连接 k8s了,现在要考虑如何创建pod代理
2. 创建pod需要 jenkins Slave 镜像
3. k8s通过这个镜像拉起 jenkins Slave
参考:https://github.com/jenkinsci/docker-jnlp-slave
1
2
3
4
5
6
# 制作镜像需要考虑的事
# 镜像中要完成: 代码拉取 代码编译 单元测试 构建镜像
1. 什么开发语言,不同的开发环境 java - mvn , go语言编译 和 python环境
2. 构建镜像 docker
3. 部署到k8s helm
4. 推送镜像
1
2
3
4
5
# java 为例
1. 代码编译: maven jdk
2. 打包镜像: docker
3. 持续部署: helm
4. slave的agent:slave.jar
1
2
3
4
# 镜像 如何 作为slave存在 
1. 传统方式就是在 jenkins 管理页面添加,添加后就可以连接到slave节点,启动agent与master交互
2. 每个slave上有个agent jar包 ,会与master实时通信,kill jar包就会不可用
3. 镜像中必须包含 slave的agent ,传统方式会自动安装的,镜像里面需要自己安装,然后才能连接master,得到下发任务
1
2
3
4
5
6
7
8
9
10
[root@k8s-master1 opt]# unzip jenkins-slave.zip 
[root@k8s-master1 opt]# cd jenkins-slave
[root@k8s-master1 jenkins-slave]# ls -l

-rw-r--r-- 1 root root 435 Dec 26 15:49 Dockerfile
-rwxr-xr-x 1 root root 37818368 Nov 13 21:39 helm
-rw-r--r-- 1 root root 1980 Nov 24 14:56 jenkins-slave
-rwxr-xr-x 1 root root 46677376 Dec 26 15:43 kubectl
-rw-r--r-- 1 root root 10409 Nov 24 14:56 settings.xml
-rw-r--r-- 1 root root 770802 Nov 24 14:56 slave.jar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# docker环境,使用数据卷挂载即可,pod在node上启动,node上都有docker ,docker in docker
# kubectl 连接 k8s 需要认证信息 也就是客户端访问
# 参考:https://github.com/jenkinsci/docker-jnlp-slave

[root@k8s-master1 jenkins-slave]# vim Dockerfile

FROM centos:7
LABEL maintainer lizhenliang

RUN yum install -y java-1.8.0-openjdk maven curl git libtool-ltdl-devel && \
yum clean all && \
rm -rf /var/cache/yum/* && \
mkdir -p /usr/share/jenkins

COPY slave.jar /usr/share/jenkins/slave.jar # agent jar包
COPY jenkins-slave /usr/bin/jenkins-slave # shell脚本 管理启动 slave.jar 他们都是官方提供的
COPY settings.xml /etc/maven/settings.xml #
RUN chmod +x /usr/bin/jenkins-slave
COPY helm kubectl /usr/bin/ # helm 二进制程序 用来部署k8s,kubectl 用来 连接k8s 查看资源状态等 操作k8s部署删除

ENTRYPOINT ["jenkins-slave"]
1
2
3
4
5
6
7
# 构建镜像
[root@k8s-master1 jenkins-slave]# docker build -t jenkins-slave:jdk-1.8 .

# 推送到harbor公共仓库中
[root@k8s-master1 jenkins-slave]# docker tag jenkins-slave:jdk-1.8 172.31.228.51/library/jenkins-slave:jdk-1.8
[root@k8s-master1 jenkins-slave]# docker login 172.31.228.51
[root@k8s-master1 jenkins-slave]# docker push 172.31.228.51/library/jenkins-slave:jdk-1.8

测试动态创建 slave

1
2
3
4
5
6
7
8
9
1. 生成创建pod 
2. jenkins pipeline 创建pod slave
3. - name: jnlp 是固定的
4. 打印hostname 查看job是不是在pod中的slave运行
# 参考: https://plugins.jenkins.io/kubernetes -> Declarative Pipeline

# 会拉取镜像 jenkins/jnlp-slave 3.35-5-alpine ccf03f44972a 3 months ago 141MB
# 同时运行两个构建 在不同的node上创建了pod去执行任务
# label 'jenkins-slave' 需要变化,不能同一时间运行同一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
pipeline {
agent {
kubernetes {
label 'jenkins-slave'
yaml """
kind: Pod
metadata:
name: jenkins-slave
spec:
containers:
- name: jnlp
image: 172.31.228.51/library/jenkins-slave:jdk-1.8
"""
}
}
parameters {
choice choices: ['172.31.228.51:9999/root/a.git', '172.31.228.51:9999/root/b.git', '172.31.228.51:9999/root/c.git'], description: '请选择要发布的项目git地址', name: 'git'
choice choices: ['172.31.228.50', '172.31.228.52', '172.31.228.53'], description: '请选择要发布到的服务器', name: 'host'
}

stages {
stage('1. 拉取代码') {
steps {
echo "${git}"
}
}

stage('2. 代码编译') {
steps {
echo 'Hello World'
}
}

stage('3. 单元测试') {
steps {
echo 'Hello World'
}
}

stage('4. 部署') {
steps {
echo "${host}"
sh "hostname"
}
}

}
}

Pipeline 集成 Helm 发布微服务项目

  1. k8s 动态创建 jenkins slave pod
  2. jenkins slave pod 完成 拉取代码 -> 代码编译 -> 单元测试 -> 构建镜像 -> Helm部署到k8s

需要的插件

  1. Git Parameter # 动态从git中后去所有分支
  2. Git # 拉取代码
  3. Pipeline # Pipeline
  4. Config File Provider # 将配置文件存放在 jenkins 里 让Pipeline引用,放到slave pod 中 (kubectl 配置文件 kubeconfig), 直接放到镜像中不太安全
  5. kubernetes # 动态创建代理
  6. Extended Choice Parameter # 扩展参数可选配置 (多选)
1
2
3
4
5
6
7
8
9
10
# 微服务 需要选择的点
1. 分支
2. 微服务名称
3. 端口
4. 命名空间
5. 副本数
6. chart模板

# 一般方法 : dp.yaml svc.yaml ing.yaml configmap.yaml 一套,使用sed 's###g' xx.yaml 替换
# helm : 一键部署/卸载 模板化部署 传参不同的环境

准备chart目录

1
2
3
4
5
6
7
[root@k8s-master1 opt]# tar -xf ms-0.1.0.tgz 
[root@k8s-master1 opt]# cd ms
[root@k8s-master1 ms]# ls -l
total 12
-rw-r--r-- 1 root root 101 Jan 1 1970 Chart.yaml
drwxr-xr-x 2 root root 4096 Jan 7 15:16 templates
-rw-r--r-- 1 root root 638 Jan 1 1970 values.yaml
1
2
3
4
5
6
7
8
# 添加 repo 
[root@k8s-master1 ms]# helm repo add --username admin --password lx@68328153 myrepo http://172.31.228.51/chartrepo/library

[root@k8s-master1 ms]# helm repo list
NAME URL
stable http://mirror.azure.cn/kubernetes/charts
aliyun https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
myrepo http://172.31.228.51/chartrepo/library
1
2
3
4
# 推送 与 安装Chart
[root@k8s-master1 opt]# helm push ms-0.1.0.tgz --username=admin --password=lx@68328153 http://172.31.228.51/chartrepo/microservice
Pushing ms-0.1.0.tgz to http://172.31.228.51/chartrepo/microservice...
Done.

准备 git 项目 和 源代码

1
gitlab创建ms项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 拉取项目到本地 
[root@k8s-master1 microservic-code]# cd /root/microservic-code
[root@k8s-master1 microservic-code]# git clone http://47.240.13.206:9999/root/ms.git

# 将dev3分支的代码 拷贝到ms下
[root@k8s-master1 microservic-code]# cp -rf simple-microservice-dev3/* ms/
[root@k8s-master1 microservic-code]# ls -l ms

# 上传代码 提交缓存区
[root@k8s-master1 microservic-code]# cd ms/
[root@k8s-master1 ms]# git add .

[root@k8s-master1 ms]# git config --global user.email "365042337@.qq.com"
[root@k8s-master1 ms]# git config --global user.name "touchlixiang"

[root@k8s-master1 ms]# git commit -m 'all'

[root@k8s-master1 ms]# git push origin master
Username for 'http://47.240.13.206:9999': root
Password for 'http://root@47.240.13.206:9999':
Counting objects: 416, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (312/312), done.
Writing objects: 100% (416/416), 758.09 KiB | 0 bytes/s, done.
Total 416 (delta 55), reused 0 (delta 0)
remote: Resolving deltas: 100% (55/55), done.
To http://47.240.13.206:9999/root/ms.git
* [new branch] master -> master

修改 Pipeline 默认变量

1
2
def registry = "172.31.228.51"
def git_url = "http://172.31.228.51:9999/root/ms.git"

创建凭据

连接 harbor

连接 gitlab

修改 Pipeline 凭据变量

1
2
3
1. 这两个凭证 点击更新 复制ID 
def harbor_registry_auth = "e2666836-7e4a-4d25-a4f0-f97a7711cda7"
def git_auth = "70ab1d82-b8cb-4dcd-a520-9b257daaf44f"

把所有的插件 安装好

1
所需插件: Git Parameter/Git/Pipeline/Config File Provider/kubernetes/Extended Choice Parameter

kubeconfig 凭据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 修改 Pipeline 凭据变量 
def k8s_auth = "c3e46862-a03a-4f8b-91ff-7a0c8f86b796"

# 需要kubeconfi配置文件
# 二进制部署 需要单独生成 使用admin证书
[root@k8s-master1 k8s]# cd /root/ansible-k8s-deploy/ssl/k8s/

[root@k8s-master1 k8s]# vim kubeconfig.sh
# 设置集群参数
kubectl config set-cluster kubernetes --server=https://172.31.228.50:6443 --embed-certs=true --certificate-authority=ca.pem --kubeconfig=config

# 设置客户端认证参数
kubectl config set-credentials cluster-admin --certificate-authority=ca.pem --embed-certs=true --client-key=admin-key.pem --client-certificate=admin.pem --kubeconfig=config

# 设置上下文参数
kubectl config set-context default --cluster=kubernetes --user=cluster-admin --kubeconfig=config

# 设置当前环境的default
kubectl config use-context default --kubeconfig=config

[root@k8s-master1 k8s]# bash kubeconfig.sh
Cluster "kubernetes" set.
User "cluster-admin" set.
Context "default" created.
Switched to context "default".

# 将生成的 config里面的所有内容 复制到 k8s_auth 的 Configuration File
[root@k8s-master1 k8s]# cat config

创建新的流水线 ms

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 别忘记修改配置文件的变量 再确认一次
#!/usr/bin/env groovy
// 所需插件: Git Parameter/Git/Pipeline/Config File Provider/kubernetes/Extended Choice Parameter
// 公共
def registry = "172.31.228.51"
// 项目
def project = "microservice"
def git_url = "http://172.31.228.51:9999/root/ms.git"
def gateway_domain_name = "gateway.ctnrs.com"
def portal_domain_name = "portal.ctnrs.com"
// 认证
def image_pull_secret = "registry-pull-secret"
def harbor_registry_auth = "e2666836-7e4a-4d25-a4f0-f97a7711cda7"
def git_auth = "70ab1d82-b8cb-4dcd-a520-9b257daaf44f"
// ConfigFileProvider ID
def k8s_auth = "c3e46862-a03a-4f8b-91ff-7a0c8f86b796"
...
1
2
3
4
5
# 将文件内容粘贴进入流水线
# 第一次构建预配置
# 第二次通过参数化构建 什么都不选 再让构建跑一次 看看能不能获取到所有分支
# 纯写 Pipeline 前1、2次会有些问题,要读取参数
# 第二次 我没有选择任何要构建的服务,运行的时候也走到了编译步骤,不会有任何部署,出现编译问题再重新试试
1
2
3
4
5
[root@k8s-master1 k8s]# kubectl get pods
NAME READY STATUS RESTARTS AGE
jenkins-6459665769-2dzp7 1/1 Running 0 8h
jenkins-slave-8810d-rffzp 1/1 Running 0 10s
nfs-client-provisioner-769f87c8f6-sdk9q 1/1 Running 0 8h

创建新的分支

1
# 修改下镜像的地址 安装时间服务太慢使用老师的镜像仓库下的镜像

部署一个服务测试

1
2
3
4
5
6
7
8
[root@k8s-master1 k8s]# kubectl get pods -n ms -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
eureka-0 1/1 Running 0 9h 10.244.2.28 k8s-master1 <none> <none>
eureka-1 1/1 Running 3 30h 10.244.1.42 k8s-node1 <none> <none>
eureka-2 1/1 Running 2 30h 10.244.0.52 k8s-node2 <none> <none>
ms-gateway-service-6bb588dbdd-tbsrb 1/1 Running 0 91s 10.244.0.55 k8s-node2 <none> <none>

# eureka 查看注册

1
2
3
[root@k8s-master1 k8s]# helm ls -n ms
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
gateway-service ms 1 2020-01-07 09:19:28.140796979 +0000 UTC deployed ms-0.1.0 0.1.0
1
# 没问题的话 把剩下的服务 一起部署

1
2
3
4
5
6
7
8
9
10
[root@k8s-master1 templates]# kubectl get pods -n ms -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
eureka-0 1/1 Running 0 9h 10.244.2.28 k8s-master1 <none> <none>
eureka-1 1/1 Running 3 30h 10.244.1.42 k8s-node1 <none> <none>
eureka-2 1/1 Running 2 30h 10.244.0.52 k8s-node2 <none> <none>
ms-gateway-service-6bb588dbdd-tbsrb 1/1 Running 0 17m 10.244.0.55 k8s-node2 <none> <none>
ms-order-service-565fb84987-pzgt9 1/1 Running 0 2m12s 10.244.0.56 k8s-node2 <none> <none>
ms-portal-service-777596f65-hhcjv 1/1 Running 0 2m16s 10.244.2.45 k8s-master1 <none> <none>
ms-product-service-6987b969-s6xbm 1/1 Running 0 2m16s 10.244.2.46 k8s-master1 <none> <none>
ms-stock-service-5b5b88865d-r9kjv 1/1 Running 0 2m2s 10.244.2.47 k8s-master1 <none> <none>

手动删除所有服务

1
2
3
4
5
6
7
8
9
10
# helm 删除 
helm ls -n ms

helm uninstall gateway-service -n ms
helm uninstall portal-service -n ms
helm uninstall order-service -n ms
helm uninstall stock-service -n ms
helm uninstall product-service -n ms

[root@k8s-master1 ms]# kubectl get pods,svc,ing -n ms
1
2
3
4
5
6
7
8
# 我修改了数据库连接地址 ms项目中的 重新Git提交 再打包一次

[root@k8s-master1 ms]# git add .
[root@k8s-master1 ms]# git commit -m 'all 2'
[root@k8s-master1 ms]# git push origin master

# 再把master的容器镜像修改一下 再重新部署看看
# 我刚才页面的问题是我代码里的 连接数据库地址没有改 再上线前一定要做好检查工作

回滚思路

1
2
3
1. 重新部署镜像 
2. jenkins 中获取 镜像仓库的镜像 tag , 脚本动态从harbor api 获取某个API下的所有镜像列表,
3. 拿到镜像列表选择 要回滚的镜像版本 重新部署