Kubernetes - DevOps
# DevOps
项目开发需要考虑的维度:
- Dev:怎么开发
- Ops:怎么运维
- 高并发:怎么承担高并发
- 高可用:怎么做到高可用
# 什么是 DevOp
DevOps: Development 和 Operations 的组合:
- DevOps 看作开发(软件工程)、技术运营和质量保障(QA)三者的交集
- 突出重视软件开发人员和运维人员的沟通合作,通过自动化流程来使得软件构建、测试、发布更加快捷、频繁和可靠
- DevOps 希望做到的是软件产品交付过程中 IT 工具链的打通,使得各个团队减少时间损耗,更加高效地协同工作。专家们总结出了下面这个 DevOps 能力图,良好的闭环可以大大增加整体的产出
# 什么是 CI & CD
持续集成(Continuous Integration)
持续集成是指软件个人研发的部分向软件整体部分交付,频繁进行集成以便更快地发现其中的错误。「持续集成」源自于极限编程(XP),是 XP 最初的 12 种实践之一。
CI 需要具备这些:
- 全面的自动化测试。这是实践持续集成&持续部署的基础,同时,选择合适的自动化测试工具也极其重要
- 灵活的基础设施。容器,虚拟机的存在让开发人员和 QA 人员不必再大费周折
- 版本控制工具。如 Git,CVS,SVN 等
- 自动化的构建和软件发布流程的工具,如 Jenkins,flow.ci
- 反馈机制。如构建/测试的失败,可以快速地反馈到相关负责人,以尽快解决达到一个更稳定的版本
持续交付(Continuous Delivery)
持续交付在持续集成的基础上,将集成后的代码部署到更贴近真实运行环境的「类生产环境」(production-like environments)中。持续交付优先于整个产品生命周期的软件部署,建立在高水平自动化持续集成之上。灰度发布。
持续交付和持续集成的优点非常相似:
- 快速发布。能够应对业务需求,并更快地实现软件价值
- 编码 -> 测试 -> 上线 -> 交付的频繁迭代周期缩短,同时获得迅速反馈
- 高质量的软件发布标准。整个交付过程标准化、可重复、可靠
- 整个交付过程进度可视化,方便团队人员了解项目成熟度
- 更先进的团队协作方式。从需求分析、产品的用户体验到交互 设计、开发、测试、运维等角色密切协作,相比于传统的瀑布式软件团队,更少浪费
持续部署(Continuous Deployment)
持续部署是指当交付的代码通过评审之后,自动部署到生产环境中。持续部署是持续交付的最高阶段。这意味着,所有通过了一系列的自动化测试的改动都将自动部署到生产环境。它也可以被称为「Continuous Release」。
开发人员提交代码,持续集成服务器获取代码,执行单元测试,根据测试结果决定是否部署到预演环境,如果成功部署到预演环境,进行整体验收测试,如果测试通过,自动部署到产品环境,全程自动化高效运转。
持续部署主要好处是,可以相对独立地部署新的功能,并能快速地收集真实用户的反馈。
You build it, you run it
,这是 Amazon 一年可以完成 5000 万次部署,平均每个工程师每天部署超过 50 次的核心秘籍。
下图是由 Jams Bowman 绘制的持续交付工具链图
# 命名空间
开始构建 DevOps 流程。
创建一个命名空间 kube-devops 实现 devops
kube-devops-namespace.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins-admin
namespace: kube-devops
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: jenkins-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: jenkins-admin
namespace: kube-devops
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
构建:
kubectl apply -f kube-devops-namespace.yaml
# Gitlab
GitLab (opens new window) 是一个用于仓库管理系统的开源项目,使用 Git 作为代码管理工具,并在此基础上搭建起来的 Web 服务。
Gitlab 是被广泛使用的基于 git 的开源代码管理平台, 基于 Ruby on Rails 构建, 主要针对软件开发过程中产生的代码和文档进行管理, Gitlab 主要针对 group 和 project 两个维度进行代码和文档管理, 其中 group 是群组, project 是工程项目, 一个 group 可以管理多个project , 可以理解为一个群组中有多项软件开发任务, 而一个 project 中可能包含多个 branch, 意为每个项目中有多个分支, 分支间相互独立, 不同分支可以进行归并。
安装GitLab官方推荐 至少 4G 的内存,否则可能会卡顿或者运行非常慢,所以建议采用 4G 以上的云服务进行测试,或者本地搭建虚拟机的方式来做。
# 安装
非 Docker 安装
# 下载安装包
wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gitlab-ce-15.9.1-ce.0.el7.x86_64.rpm
# 安装
rpm -i gitlab-ce-15.9.1-ce.0.el7.x86_64.rpm
# 编辑 /etc/gitlab/gitlab.rb 文件
# 修改 external_url 访问路径 http://<ip>:<port>
# 其他配置修改如下
gitlab_rails['time_zone'] = 'Asia/Shanghai'
puma['worker_processes'] = 2
sidekiq['max_concurrency'] = 8
postgresql['shared_buffers'] = "128MB"
postgresql['max_worker_processes'] = 4
prometheus_monitoring['enable'] = false
# 更新配置并重启
gitlab-ctl reconfigure
gitlab-ctl restart
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Docker 安装
下载 Gitlab
docker pull gitlab/gitlab-ce
配置,数据,日志进行持久化,即使用数据卷,先创建三个目录
mkdir -p /docker/gitlab/etc
mkdir -p /docker/gitlab/log
mkdir -p /docker/gitlab/data
2
3
启动 Gitlab
docker run -d --name gitlab \
-p 8051:443 \
-p 8050:80 \
--hostname 192.168.199.31 \
--restart=always \
-v /docker/gitlab/etc:/etc/gitlab \
-v /docker/gitlab/log:/var/log/gitlab \
-v /docker/gitlab/data:/var/opt/gitlab \
-v /etc/localtime:/etc/localtime:ro \
gitlab/gitlab-ce:latest
2
3
4
5
6
7
8
9
10
然后前往 docker/gitlab/etc
,编辑 gitlab.rb
文件。
修改如下内容:
external_url 'http://192.168.199.31' # 因为是容器内部,所以不需要填写端口,默认 80
gitlab_rails['time_zone'] = 'Asia/Shanghai'
puma['worker_processes'] = 2 # 或者 1
sidekiq['max_concurrency'] = 8 # 或者 2
postgresql['shared_buffers'] = "128MB" # 或者 64MB
postgresql['max_worker_processes'] = 4 # 或者 2
prometheus_monitoring['enable'] = false
2
3
4
5
6
7
浏览器访问:http://192.168.199.31:8050/
# 页面配置
访问 Gitlab 后,我们可以进行一些页面的初始化配置:
# 如果是 Docker 安装,则进入 Gitlab 容器
docker exec -it gitlab bash
# 查看默认密码
cat /etc/gitlab/initial_root_password
# 登录后修改默认密码 > 右上角头像 > Perferences > Password
# 修改系统配置:点击左上角三横 > Admin
# Settings > General > Account and limit > 取消 Gravatar enabled > Save changes
# 关闭用户注册功能
# Settings > General > Sign-up restrictions > 取消 Sign-up enabled > Save changes
# 开启 webhook 外部访问
# Settings > Network > Outbound requests > Allow requests to the local network from web hooks and services 勾选
# 设置语言为中文(全局)
# Settings > Preferences > Localization > Default language > 选择简体中文 > Save changes
# 设置当前用户语言为中文
# 右上角用户头像 > Preferences > Localization > Language > 选择简体中文 > Save changes
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 配置 Secret
创建 Gitlab 的一个默认用户名密码 secret 给 Jenkins,方便 Jenkins 后续实现自动化部署,快速访问 Gitlab。
因为后面 Jenkins 在 k8s 部署,所以利用 k8s 的 secret 来管理用户名和密码。
cd /opt/gitlab
echo root > ./username
echo youngkbt > password
kubectl create secret generic git-username-password --from-file=./username --from-file=./password -n kube-devops
2
3
4
5
# 为项目配置 Webhook
进入项目点击侧边栏设置 > Webhooks 进入配置即可。
URL:在 Jenkins 创建 pipeline 项目后(下面会按照 Jenkins)
触发来源:
- 推送事件:表示收到新的推送代码就会触发
- 标签推送事件:新标签推送才会触发
- 评论:根据评论决定触发
- 合并请求事件:创建、更新或合并请求触发
添加成功后,可以在下方点击测试按钮查看 jenkins 是否成功触发构建操作。
# 卸载
# 停止服务
gitlab-ctl stop
# 卸载 rpm 软件(注意安装的软件版本是 ce 还是 ee)
rpm -e gitlab-ce
# 查看进程
ps -ef|grep gitlab
# 干掉第一个 runsvdir -P /opt/gitlab/service log 进程
# 删除 gitlab 残余文件
find / -name *gitlab* | xargs rm -rf
find / -name gitlab | xargs rm -rf
2
3
4
5
6
7
8
9
10
11
12
13
# Harbor
# 安装
# 下载 harbor 安装包
wget https://github.com/goharbor/harbor/releases/download/v2.8.2/harbor-offline-installer-v2.8.2.tgz
# 解压后执行 install.sh 就行
tar -zxvf harbor-offline-installer-v2.8.2.tgz
cd harbor
2
3
4
5
6
7
复制配置模版
cp harbor.yml.tmpl harbor.tmpl
进入配置文件 harbor.tmpl,修改 hostname 为本机地址,如 192.168.199.31
。注释掉 https(如果不申请证书,只用 http 必须注释掉。否则会自动找 https 证书从而报错)。可以修改端口 port,默认是 80,我修改为 8040。其他保持默认。
安装 harbor
./install.sh
安装完成后检查服务是否起来
docker-compose ps
浏览器访问:http://192.168.199.31:8040
,默认账号密码:admin / Harbor12345
,密码在harbor.yml文件中可修改。
# Harbar 自启
创建配置文件
vim /etc/systemd/system/harbor.service
添加如下内容
[Unit]
Description=Harbor
After=docker.service systemd-networkd.service systemd-resolved.service
Requires=docker.service
Documentation=http://github.com/vmware/harbor
[Service]
Type=simple
Restart=on-failure
RestartSec=5
ExecStart=/usr/local/bin/docker-compose -f /usr/local/harbor/docker-compose.yml up
ExecStop=/usr/local/bin/docker-compose -f /usr/local/harbor/docker-compose.yml down
# ExecStart=/usr/local/bin/docker-compose -f {{ harbor_install_path }}/harbor/docker-compose.yml up
# ExecStop=/usr/local/bin/docker-compose -f {{ harbor_install_path }}/harbor/docker-compose.yml down
[Install]
WantedBy=multi-user.target
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
其中 换成自己的 harbor 安装路径。 还有 docker-compose 的绝对路径,请通过
which docker-compose
查看。
然后启动该项服务:
sudo systemctl enable harbor
sudo systemctl start harbor
2
# 配置 Secret
创建 Harbor 的一个默认用户名密码 secret 给 Jenkins,方便 Jenkins 后续实现自动化部署,快速访问 Harbor。
因为 k8s 去 Harbor 拉去镜像需要登录,所以利用 k8s 的 secret 来管理用户名和密码。
# 创建 harbor 访问账号密码(需要将下访问的配置信息改成你自己的)
kubectl create secret docker-registry harbor-secret --docker-server=192.168.199.31:8040 --docker-username=admin --docker-password=YoungKbt1234 -n kube-devops
2
如果某个服务器需要连接 Harbar,则需要添加 Harbar 的地址。
vim /etc/docker/daemon.json
在文件中添加如下内容:
{
"insecure-registries": ["192.168.199.31:8040"]
}
2
3
重新加载 daemon:
systemctl daemon-reload
systemctl restart docker
2
# SonarQube
这里 SonarQube 安装 k8s 里面。
# 安装
创建 sonarqube.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sonarqube-data
namespace: kube-devops
spec:
accessModes:
- ReadWriteMany
storageClassName: "managed-nfs-storage" # 前面有常见过这个制备器
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sonarqube
namespace: kube-devops
labels:
app: sonarqube
spec:
replicas: 1
selector:
matchLabels:
app: sonarqube
template:
metadata:
labels:
app: sonarqube
spec:
initContainers:
- name: init-sysctl
image: busybox:1.28.4
command: ["sysctl", "-w", "vm.max_map_count=262144"]
securityContext:
privileged: true
containers:
- name: sonarqube
image: sonarqube
ports:
- containerPort: 9000
env:
- name: SONARQUBE_JDBC_USERNAME # 数据库用户名
value: "sonarUser"
- name: SONARQUBE_JDBC_PASSWORD # 数据库密码
value: "123456"
- name: SONARQUBE_JDBC_URL
value: "jdbc:postgresql://postgres-sonar:5432/sonarDB"
livenessProbe:
httpGet:
path: /sessions/new
port: 9000
initialDelaySeconds: 60
periodSeconds: 30
readinessProbe:
httpGet:
path: /sessions/new
port: 9000
initialDelaySeconds: 60
periodSeconds: 30
failureThreshold: 6
volumeMounts:
- mountPath: /opt/sonarqube/conf
name: data
- mountPath: /opt/sonarqube/data
name: data
- mountPath: /opt/sonarqube/extensions
name: data
volumes:
- name: data
persistentVolumeClaim:
claimName: sonarqube-data
---
apiVersion: v1
kind: Service
metadata:
name: sonarqube
namespace: kube-devops
labels:
app: sonarqube
spec:
type: NodePort
ports:
- name: sonarqube
port: 9000
targetPort: 9000
protocol: TCP
selector:
app: sonarqube
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
84
85
86
87
88
89
pgsql.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: kube-devops
spec:
accessModes:
- ReadWriteMany
storageClassName: "managed-nfs-storage"
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres-sonar
namespace: kube-devops
spec:
replicas: 1
selector:
matchLabels:
app: postgres-sonar
template:
metadata:
labels:
app: postgres-sonar
spec:
containers:
- name: postgres-sonar
image: postgres:14.2
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: "sonarDB"
- name: POSTGRES_USER
value: "sonarUser"
- name: POSTGRES_PASSWORD
value: "123456"
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
persistentVolumeClaim:
claimName: postgres-data
---
apiVersion: v1
kind: Service
metadata:
name: postgres-sonar
namespace: kube-devops
labels:
app: postgres-sonar
spec:
type: NodePort
ports:
- name: postgres-sonar
port: 5432
targetPort: 5432
protocol: TCP
selector:
app: postgres-sonar
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
部署 sonarqube
# 进入 /opt/k8s/devops
kubectl apply -f sonarqube/
2
# 生成服务 token
# 登录到 sonarqube 后台,点击头像 > MyAccount > Security > Generate Tokens > generate 生成 token 并复制
# 创建 Webhook 服务
# 点击顶部菜单栏的配置 > 配置(小三角) > 网络调用
Name:wolfcode-jenkins
URL:http://<sonar ip>:<sonar port>/sonarqube-webhook/
2
3
# 创建项目
# SonarQube 顶部菜单栏 Projects > Create new project > 配置基础信息并保存 > Provide a token > Generate 生成 token > Continue
# 分别选择 Java / Maven 后,按照脚本配置 Jenkinsfile 中的 sonar 配置信息
mvn sonar:sonar -Dsonar.projectKey=k8s-cicd-demo
2
3
4
# Jenkins
# 构建带 maven 环境的 jenkins 镜像
准备 apache-maven-3.9.0-bin.tar.gz、sonar-scanner-4.8.0.2856-linux.tar.gz 两个压缩包,然后创建 DockerFile
FROM jenkins/jenkins:2.392-jdk11
ADD ./apache-maven-3.9.0-bin.tar.gz /usr/local/
ADD ./sonar-scanner-4.8.0.2856-linux.tar.gz /usr/local/
USER root
WORKDIR /usr/local/
RUN mv sonar-scanner-4.8.0.2856-linux sonar-scanner-cli
RUN ln -s /usr/local/sonar-scanner-cli/bin/sonar-scanner /usr/bin/sonar-scanner
ENV MAVEN_HOME=/usr/local/apache-maven-3.9.0
ENV PATH=$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH
RUN echo "jenkins ALL=NOPASSWD: ALL" >> /etc/sudoers
USER jenkins
2
3
4
5
6
7
8
9
10
11
12
13
14
15
构建镜像到搭建的 Harbar
# 构建带 maven 环境的 jenkins 镜像
docker build -t 192.168.199.31:8040/library/jenkins-maven:v1 .
# 登录 harbor
docker login -uadmin 192.168.199.31:8040
# 推送镜像到 harbor
docker push 192.168.199.31:8040/library/jenkins-maven:v1
2
3
4
5
6
7
8
# 安装 Jenkins
创建 manifests 目录,在该目录下创建几个 yaml 文件:
jenkins-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mvn-settings
namespace: kube-devops
labels:
app: jenkins-server
data:
settings.xml: |-
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository>/var/jenkins_home/repository</localRepository>
<mirrors>
<mirror>
<id>aliyun-central</id>
<name>aliyun maven</name>
<mirrorOf>*</mirrorOf>
<url>https://maven.aliyun.com/repository/central/</url>
</mirror>
<mirror>
<id>aliyun-public</id>
<name>aliyun maven</name>
<mirrorOf>*</mirrorOf>
<url>https://maven.aliyun.com/repository/public/</url>
</mirror>
<mirror>
<id>aliyun-apache-snapshots</id>
<name>aliyun maven</name>
<mirrorOf>*</mirrorOf>
<url>https://maven.aliyun.com/repository/apache-snapshots</url>
</mirror>
<mirror>
<id>aliyun-google</id>
<name>aliyun maven</name><mirrorOf>*</mirrorOf>
<url>https://maven.aliyun.com/repository/google</url>
</mirror>
</mirrors>
<pluginGroups>
<pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
</pluginGroups>
<profiles>
<profile>
<id>sonar</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<sonar.host.url>http://sonarqube:9000</sonar.host.url>
</properties>
</profile>
</profiles>
</settings>
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
jenkins-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins
namespace: kube-devops
spec:
replicas: 1
selector:
matchLabels:
app: jenkins-server
template:
metadata:
labels:
app: jenkins-server
spec:
serviceAccountName: jenkins-admin
imagePullSecrets:
- name: harbor-secret # harbor 访问 secret
containers:
- name: jenkins
image: 192.168.199.31:8040/library/jenkins-maven:v1
imagePullPolicy: IfNotPresent
securityContext:
privileged: true
runAsUser: 0 # 使用 root 用户运行容器
resources:
limits:
memory: "2Gi"
cpu: "1000m"
requests:
memory: "500Mi"
cpu: "500m"
ports:
- name: httpport
containerPort: 8080
- name: jnlpport
containerPort: 50000
livenessProbe:
httpGet:
path: "/login"
port: 8080
initialDelaySeconds: 90
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 5
readinessProbe:
httpGet:
path: "/login"
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
volumeMounts:
- name: jenkins-data
mountPath: /var/jenkins_home
- name: docker
mountPath: /run/docker.sock
- name: docker-home
mountPath: /usr/bin/docker
- name: mvn-setting
mountPath: /usr/local/apache-maven-3.9.0/conf/settings.xml
subPath: settings.xml
- name: daemon
mountPath: /etc/docker/daemon.json
subPath: daemon.json
- name: kubectl
mountPath: /usr/bin/kubectl
volumes:
- name: kubectl
hostPath:
path: /usr/bin/kubectl
- name: jenkins-data
persistentVolumeClaim:
claimName: jenkins-pvc
- name: docker
hostPath:
path: /run/docker.sock # 将主机的 docker 映射到容器中
- name: docker-home
hostPath:
path: /usr/bin/docker
- name: mvn-setting
configMap:
name: mvn-settings
items:
- key: settings.xml
path: settings.xml
- name: daemon
hostPath:
path: /etc/docker/
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
84
85
86
87
88
89
90
jenkins-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jenkins-pvc
namespace: kube-devops
spec:
storageClassName: managed-nfs-storage
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
2
3
4
5
6
7
8
9
10
11
12
jenkins-service.yaml
apiVersion: v1
kind: Service
metadata:
name: jenkins-service
namespace: kube-devops
annotations:
prometheus.io/scrape: 'true'
prometheus.io/path: /
prometheus.io/port: '8080'
spec:
selector:
app: jenkins-server
type: NodePort
ports:
- port: 8080
targetPort: 8080
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
jenkins-serviceAccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins-admin
namespace: kube-devops
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: jenkins-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: jenkins-admin
namespace: kube-devops
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
进入 jenkins 目录,安装 jenkins
# 进入 jenkins 目录,安装 jenkins
kubectl apply -f manifests/
# 查看是否运行成功
kubectl get po -n kube-devops
# 查看 service 端口,通过浏览器访问
kubectl get svc -n kube-devops
# 查看容器日志,获取默认密码
kubectl logs -f pod名称 -n kube-devops
2
3
4
5
6
7
8
9
10
11
# 安装插件
如果有些插件搜不到,可以去官网下载 https://plugins.jenkins.io/
。
Build Authorization Token Root
构建授权 token
Gitlab
Gitlab 配置插件
SonarQube Scanner
代码质量审查工具
在 Dashboard > 系统管理 > Configure System 下面配置 SonarQube servers
Name:sonarqube # 注意这个名字要在 Jenkinsfile 中用到 Server URL:http://sonarqube:9000
Server authentication token:创建 credentials 配置为从 sonarqube 中得到的 token
进入系统管理 > 全局工具配置 > SonarQube Scanner > Add SonarQube Scanner
Name:sonarqube-scanner
自动安装:取消勾选
SONAR_RUNNER_HOME:/usr/local/sonar-scanner-cli
Node and Label parameter
节点标签参数配置
Kubernetes
jenkins + k8s 环境配置
进入 Dashboard > 系统管理 > 节点管理 > Configure Clouds 页面
配置 k8s 集群,名称:kubernetes,点击 Kubernetes Cloud details 继续配置
Kubernetes 地址:如果 jenkins 是运行在 k8s 容器中,直接配置服务名即可 https://kubernetes.default
如果 jenkins 部署在外部,那么则不仅要配置外部访问 ip 以及 apiserver 的端口(6443),还需要配置服务证书
Jenkins 地址:
- 如果部署在 k8s 集群内部:
http://jenkins-service:8080
- 如果在外部:
http://192.168.192.168:32479
(换成你们自己的)
配置完成后保存即可
Config File Provider
用于加载外部配置文件,如 Maven 的 settings.xml 或者 k8s 的 kubeconfig 等。
Git Parameter
git 参数插件,在进行项目参数化构建时使用。
# 创建 Gitlab 访问凭证
系统管理 > 安全 > Manage Credentials > System > 全局凭据(unrestricted) > Add Credentials
范围:全局
用户名:root
密码:youngkbt
ID:gitlab-user-pass
# 案例:SpringBoot 项目 CICD
点击创建任务 > Pipeline(流水线)。
参数化构建:可以通过配置参数化构建,将经常变动的参数提取出来,方便每次构建时传入。
构建触发器:勾选基于 gitlab webhook 的构建方式,同时拿到 webhook 地址,且在高级中生成 secret token,将其配置到 gitlab 项目中。
根据需求选择 webhook 的触发事件。
流水线:选择定义为 Pipeline script from SCM 从远程仓库拉取 Jenkinsfile 配置。
配置 SCM 为 Git。
Repositories:
- Repository URL:仓库地址
- Credentials:仓库访问的账号密码
- Branches to build:选择拉取哪个分支下的代码
- 脚本路径:Jenkinsfile 脚本文件名称以及所在路径
PS:当项目下载下来后,所有操作都默认在项目根目录下执行。
配置节点标签
系统管理 > 节点管理 > 列表中 master 节点最右侧的齿轮按钮
修改标签的值与项目中 Jenkinsfile 中 agent > kubernetes > label 的值相匹配
创建流水线项目
在首页点击 Create a Job 创建一个流水线风格的项目
Webhook 构建触发器
Jenkins 流水线项目 Webhook 配置
在 Jenkins 项目配置下找到构建触发器栏目
勾选 Build when a change is pushed to GitLab. GitLab webhook URL: http://192.168.113.121:31216/project/k8s-cicd-demo
上方的 URL 就是用于配置到 gitlab 项目 webhook 的地址。
启用 Gitlab 构建触发器:
- Push Events:勾选,表示有任意推送到 Git 仓库的操作都会触发构建
- Opend Merge Request Events:勾选,表示有请求合并时触发构建
点击高级 > Secret Token > Generate 按钮,生成 token
保存以上配置
GitLab 项目 Webhook 配置
进入 GitLab 项目设置界面 > Webhooks
将上方 Jenkins 中的 URL 配置到 URL 处
将上方生成的 Secret Token 配置到 Secret 令牌
按照需求勾选触发来源,这里我依然勾选 推送事件、合并请求事件
取消 SSL 验证
点击添加 webhook 按钮,添加后可以点击测试确认链接是否可以访问
Pipeline 脚本配置
流水线:选择定义为 Pipeline script from SCM 从远程仓库拉取 Jenkinsfile 配置。
配置 SCM 为 Git。
Repositories:
- Repository URL:仓库地址
- Credentials:仓库访问的账号密码
- Branches to build:选择拉取哪个分支下的代码
- 脚本路径:Jenkinsfile 脚本文件名称以及所在路径
检查/创建相关凭证
Harbor 镜像仓库凭证
通过系统管理 > Manage Credentials > 凭据 > System > 全局凭证 > Add Credentials 添加 Username with password 类型凭证
填写好用户名密码后,需要注意凭证 id 要与 Jenkinsfile 中的 *DOCKER_CREDENTIAL_ID* 一致
Gitlab 访问凭证
通过系统管理 > Manage Credentials > 凭据 > System > 全局凭证 > Add Credentials 添加 Username with password 类型凭证
填写好用户名密码后,需要注意凭证 id 要与 Jenkinsfile 中的 GIT_CREDENTIAL_ID
一致
kubeconfig 文件 id
- 事先安装 Config File Provider 插件
- 进入系统管理 > Mapped files > Add a new Config 添加配置文件
- Type 选择 Custom file 点击 next
- 在 k8s master 节点执行 cat ~/.kube/config 查看文件内容,并将所有内容复制
- 将复制的内容贴到 Config file 的 Content 中后点击 Submit 保存并提交
- 复制保存后文件 id 到 Jenkinsfile 中的
KUBECONFIG_CREDENTIAL_ID
处
SonarQube 凭证
- 进入 SonarQube 系统,点击右上角用户头像 > 我的账号 进入设置页面
- 点击 安全 > 填写令牌名称 > 点击生成按钮生成 token > 复制生成后的 token
- 进入 jenkins 添加凭证管理页面,添加 Secret Text 类型的凭证,将 token 贴入其中
- 保证凭证 id 与 Jenkinsfile 文件中的
SONAR_CREDENTIAL_ID
一致
添加 SonarQube Webhook
进入 SonarQube 管理页面,点击顶部菜单栏的配置 > 配置(小三角) > 网络调用
点击右侧创建按钮创建新的 Webhook,并填写名称与地址
名称:jenkins 地址:
http://jenkins访问ip:端口/sonarqube-webhook/
项目构建
方式一:在 Jenkins 管理后台,进入项目中点击立即构建进行项目构建。
方式二:在开发工具中修改代码,并将代码提交到远程仓库自动触发构建。
# Jenkinsfile
一般我们都会在项目根目录创建一个 Jenkinsfile 文件和 Dockerfile 文件,让 Jenkins 按照 Jenkinsfile 的规则去触发 CICD,让 Docker 按照 Dockerfile 文件去构建镜像。
这里给一个模板:
pipeline {
agent {
kubernetes {
label "maven" // 指定有 maven 标签的节点,Dashboard -> 系统管理 -> 节点列表,匹配有 maven 标签的节点
}
}
environment {
REGISTRY = "192.168.199.31:8040" // Harbor 仓库
DOCKER_CREDENTIAL_ID = "harbor-username-password" // Jenkins 全局凭据 ID:Harbor 登录的用户名密码
DOCKERHUB_NAMESPACE = "library" // Harbar 仓库
GIT_REPO_URL = "192.168.199.31:8050" // Gitlab 仓库地址
GIT_CREDENTIAL_ID = "gitlab-username-password" // Jenkins 全局凭据 ID:Gitlab 登录的用户名密码
GIT_ACCOUNT = "root" // Gitlab 用户名
APP_NAME = "test-jenkins" // 部署的项目名
SONAR_SERVER_URL = "192.168.199.28:30112" // sonarqube 质量检测地址
SONAR_CREDENTIAL_ID = "sonarqube-token" // Jenkins 全局凭据 ID:sonarqube 的认证 token
KUBE_CONFIG_CREDENTIAL_ID = "f1fd50fc-f462-42b8-8aa5-8055fdca05dd"
}
// 可选的参数构建
parameters {
gitParameter name: "BRANCH_NAME", branch: "", branchFilter: "origin/(.*)", defaultValue: "master", description: "请选择要发布的分支", quickFilterEnabled: false, selectedValue: "DEFAULT", sortMode: "NONE", tagFilter: "*", type: "PT_BRANCH"
choice(name: "NAMESPACE", choices: ["kube-devops"], description: "命名空间")
string(name: "TAG_NAME", defaultValue: "snapshot", description: "标签名称,必须以 v 开头,例如:v1、v1.0.0")
}
stages {
stage("checkout branch") {
steps {
checkout scmGit(branches: [[name: "$BRANCH_NAME"]], extensions: [], userRemoteConfigs: [[credentialsId: "$GIT_CREDENTIAL_ID", url: "http://$GIT_REPO_URL/$GIT_ACCOUNT/test-jenkins.git"]])
}
}
stage("unit test") {
steps {
sh "mvn clean test" // Maven 单元测试
}
}
// 代码质量检查
stage("sonarqube analysis") {
agent none
steps {
// sonarqube 认证,连接服务端进行代码质量检查
withCredentials([string(credentialsId: "$SONAR_CREDENTIAL_ID", variable: "SONAR_TOKEN")]) {
withSonarQubeEnv("sonarqube") {
sh '''
mvn sonar:sonar -Dsonar.projectKey=$APP_NAME
echo "mvn sonar:sonar -Dsonar.projectKey=$APP_NAME"
'''
}
// 代码质量检测超时为 5 分组
timeout(time: 5, unit: "MINUTES", activity: true) {
waitForQualityGate "true"
}
}
}
}
// 构建进行,推到 Harbor,版本跟随 Jenkins 的构建次数变化
stage("build & push") {
steps {
sh "mvn clean package -DskipTests" // 跳过单元测试,上面已经单元测试了
// 构建镜像,tag 为 Jenkins 内置的 BUILD_NUMBER,即 Jenkins 的构建次数
sh "docker build -f Dockerfile -t $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BUILD_NUMBER ."
// 认证 Harbor,推到 Harbor
withCredentials([usernamePassword(credentialsId: "$DOCKER_CREDENTIAL_ID", usernameVariable: "DOCKER_USERNAME", passwordVariable: "DOCKER_PASSWORD")]) {
sh '''
echo "$DOCKER_PASSWORD" | docker login $REGISTRY -u "$DOCKER_USERNAME" --password-stdin
docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BUILD_NUMBER
'''
}
}
}
// 推到 Harbor,版本是 latest
stage("push latest") {
when {
branch "master" // master 分支才往下触发该 stage
}
steps {
sh "docker tag $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BUILD_NUMBER $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:latest"
sh "docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:latest"
}
}
// k8s 测试环境部署
stage("deploy to dev") {
steps {
input(id: "deploy-to-dev", message: "deploy to dev?") // Jenkins 构建到这会阻塞,等待用户选择是否才继续往下执行
// 替换 deploy 下的 yaml 文件,将预先占位的字符串改成动态参数
sh '''
sed -i'' "s#REGISTRY#$REGISTRY#" deploy/test-jenkins-dev.yaml
sed -i'' "s#DOCKERHUB_NAMESPACE#$DOCKERHUB_NAMESPACE#" deploy/test-jenkins-dev.yaml
sed -i'' "s#APP_NAME#$APP_NAME#" deploy/test-jenkins-dev.yaml
sed -i'' "s#BUILD_NUMBER#$BUILD_NUMBER#" deploy/test-jenkins-dev.yaml
kubectl apply -f deploy/test-jenkins-dev.yaml
'''
}
}
// 在 Gitlab 发布 tag
stage("push with tag") {
agent none
when {
expression {
params.TAG_NAME =~ /v.*/ // 只匹配版本为 v. 开头的 jar 版本,对应上面「可选的参数构建」
}
}
steps {
input(message: "release image with tag?", submitter: "")
withCredentials([usernamePassword(credentialsId: "$GIT_CREDENTIAL_ID", usernameVariable: "GIT_USERNAME", passwordVariable: "GIT_PASSWORD")]) {
sh "git config --global user.email 'kele-Bingtang@qq.com'"
sh "git config --global user.name 'kele-Bingtang'"
sh "git tag -a $TAG_NAME -m '$TAG_NAME'"
sh "git push http://$GIT_USERNAME:$GIT_PASSWORD@$GIT_REPO_URL/$GIT_ACCOUNT/test-jenkins.git --tags --ipv4"
}
sh "docker tag $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BUILD_NUMBER $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:$TAG_NAME"
sh "docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:$TAG_NAME"
}
}
// k8s 生产环境部署
stage("deploy to production") {
agent none
when {
expression {
params.TAG_NAME =~ /v.*/
}
}
steps {
input(message: "deploy to production?", submitter: "")
sh '''
sed -i\"\" "s#REGISTRY#$REGISTRY#" deploy/test-jenkins.yaml
sed -i\"\" "s#DOCKERHUB_NAMESPACE#$DOCKERHUB_NAMESPACE#" deploy/test-jenkins.yaml
sed -i\"\" "s#APP_NAME#$APP_NAME#" deploy/test-jenkins.yaml
sed -i\"\" "s#TAG_NAME#$TAG_NAME#" deploy/test-jenkins.yaml
kubectl apply -f deploy/test-jenkins.yaml
'''
}
}
}
}
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
Dockerfile 文件
## 基础镜像
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
FROM eclipse-temurin:8-jre
## 作者
LABEL org.opencontainers.image.authors="Kele-BingTang"
## 创建并进入工作目录
RUN mkdir -p /java
WORKDIR /java
## maven 插件构建时得到 buildArgs 中的值
COPY target/*.jar app.jar
## 设置 TZ 时区
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms256m -Xmx256m"
## 暴露端口
EXPOSE 8080
## 容器启动命令
## CMD 第一个参数之后的命令可以在运行时被替换
CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24