二十一、DevOps:从零建设基于K8s的DevOps平台(二)
- 二十一、DevOps:从零建设基于K8s的DevOps平台(二)
- 1、DevOps 平台建设
- 1.1 DevOps 流程
- 1.2 DevOps 平台整体设计
- 1.3 集群规划(学习测试环境)
- 1.4 Harbor 私有仓库
- 1.4.1 安装docker
- 1.4.2 配置docker-compose
- 1.4.3 安装 Harbor
- 1.4.4 创建开机自启动脚本
- 1.4.5 docker 配置 insecure registry
- 1.4.6 Containerd 配置 insecure registry (k8s节点)
- 1.5 Jenkins 安装
- 1.5.1 安装 Docker
- 1.5.2 安装Jenkins
- 1.5.3 插件安装
- 1.5.4 docker配置insecure registry
- 1.6 GitLab 安装
- 1.6.1 关闭机器防火墙和 selinux
- 1.6.2 安装gitlab
- 1.6.3 创建项目
- 1.6.4 配置密钥
- 1.7 Jenkins 凭证 Credentials
- 1.7.1 配置 Harbor 账号密码
- 1.7.2 配置 Kubernetes 证书
- 1.7.3 配置 GitLab Key
- 1.8 配置 Agent
- 1.9 Jenkins 配置 Kubernetes 多集群
- 2、自动化流水线设计
- 2.1 自动化流水线统一模板
- 2.2 Workspace 持久化
- 2.3 Kaniko 配置文件处理
- 3、自动化构建 Java 应用
- 3.1 创建 Java 测试用例
- 3.2 定义 Jenkinsfile
- 3.3 定义 Dockerfile
- 3.4 定义 Kubernetes 资源
- 3.5 创建 Jenkins 任务(Job)
- 4、 自动化构建 Vue/H5 前端应用
- 4.1 定义 Jenkinsfile
- 4.2 定义 Dockerfile
- 4.3 定义 Kubernetes 资源
- 4.4 创建 Jenkins Job
- 5、自动化构建 Golang 项目
- 5.1 定义 Jenkinsfile
- 5.2 定义 Dockerfile
- 5.3 定义 Kubernetes 资源
- 5.4 创建 Jenkins Job
- 6、自动触发构建
- 7、一次构建多次部署
- 8、集成 Helm 发布
- 8.1 生成 Helm 模板
- 8.2 流水线集成 Helm
- 1、DevOps 平台建设
1、DevOps 平台建设
1.1 DevOps 流程
微服务发版的自动化流水线,一般会有如下步骤:
1、在 GitLab 中创建对应的项目;
2、配置 Jenkins 集成 Kubernetes 集群,后期 Jenkins 的 Slave 将为在 Kubernetes 中动态创建的 Slave;
3、Jenkins 创建对应的任务(Job),集成该项目的 Git 地址和 Kubernetes 集群;
4、开发者将代码提交到 GitLab;
5、如有配置钩子,推送(Push)代码会自动触发 Jenkins 构建,如没有配置钩子,需要手动构建;
6、Jenkins 控制 Kubernetes(使用的是 Kubernetes 插件)创建 Jenkins Slave(Pod形式);
7、Jenkins Slave 根据流水线(Pipeline)定义的步骤执行构建,生成交付物;
8、通过 Dockerfile 生成镜像;
9、将镜像提送(Push)到私有 Harbor(或者其它的镜像仓库);
10、Jenkins 再次控制 Kubernetes 进行最新的镜像部署;
11、流水线结束删除 Jenkins Slave。
1.2 DevOps 平台整体设计
1.3 集群规划(学习测试环境)
主机名称 | 物理IP | 系统 | 资源配置 | 说明 |
---|---|---|---|---|
k8s-master01 | 192.168.200.50 | Rocky9.4 | 4核4g | Master节点 |
k8s-node01 | 192.168.200.51 | Rocky9.4 | 4核4g | Node01节点 |
k8s-node02 | 192.168.200.52 | Rocky9.4 | 4核4g | Node02节点 |
Harbor | 192.168.200.53 | Rocky9.4 | 2核4g | 镜像仓库 |
Jenkins | 192.168.200.54 | Rocky9.4 | 2核4g | CI/CD流水线 |
GitLab | 192.168.200.55 | Rocky9.4 | 2核4g | 代码仓库 |
1.4 Harbor 私有仓库
1.4.1 安装docker
# 添加docker的yum源配置文件
[root@harbor ~]# yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo# 安装docker
[root@harbor ~]# yum install docker-ce docker-ce-cli -y# 启动docker
[root@harbor ~]# systemctl daemon-reload && systemctl enable --now docker
1.4.2 配置docker-compose
# 设置软连接
[root@harbor ~]# ln -s /usr/libexec/docker/cli-plugins/docker-compose /usr/local/bin/[root@harbor ~]# docker-compose -v
Dcurl -L https://github.com/docker C/compose version v2.36.2# 批量停止harbor实例
docker-compose down
# 批量启动harbor实例
docker-compose up -d
1.4.3 安装 Harbor
# 下载到本地
[root@harbor ~]# wget https://github.com/goharbor/harbor/releases/download/v2.13.1/harbor-offline-installer-v2.13.1.tgz# 解压tar包
[root@harbor ~]# tar xf harbor-offline-installer-v2.13.1.tgz
[root@harbor ~]# cd harbor# 加载配置
[root@harbor harbor]# docker load -i harbor.v2.13.1.tar.gz # 修改 Harbor 默认配置文件
[root@harbor harbor]# cp harbor.yml.tmpl harbor.yml
[root@harbor harbor]# vim harbor.yml[root@harbor harbor]# sed -n "5p;10p;13,18p;47p;66p" harbor.yml
hostname: 192.168.200.53 port: 80
# https:# https port for harbor, default is 443# port: 443# The path of cert and key files for nginx# certificate: /your/certificate/path# private_key: /your/private/key/path
harbor_admin_password: Harbor12345
data_volume: /data/harbor
- hostname:Harbor 的访问地址,可以是域名或者 IP,生产推荐使用域名,并且带有证书
- https:域名证书的配置,生产环境需要配置权威证书供 Harbor 使用,否则需要添加
insecure-registry
配置,由于是学习环境,所以本示例未配置证书- harbor_admin_password:账号密码按需修改即可,默认为 admin:Harbor12345
- data_volume:Harbor 的数据目录
# 创建 Harbor 数据目录并进行预配置:
[root@harbor harbor]# mkdir /data/harbor -p# 加载配置启动
[root@harbor harbor]# ./prepare# 执行安装:
[root@harbor harbor]# ./install.sh
web界面访问
登录后,创建一个项目
1.4.4 创建开机自启动脚本
[root@harbor harbor]# vim /etc/systemd/system/harbor.service
[root@harbor harbor]# cat /etc/systemd/system/harbor.service
[Unit]
Description=Harbor Service
After=docker.service
Requires=docker.service[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/usr/local/harbor
ExecStart=/usr/local/bin/docker-compose up -d
ExecStop=/usr/local/bin/docker-compose down
User=root[Install]
WantedBy=multi-user.target# 加上执行权限
[root@harbor harbor]# chmod +x /etc/systemd/system/harbor.service # 设置开机自启动
[root@harbor harbor]# systemctl daemon-reload
[root@harbor harbor]# systemctl enable harbor
1.4.5 docker 配置 insecure registry
如果配置不是 https 协议,所有的 Kubernetes 节点的 Docker 都需要添加 insecure-registries 配置
[root@harbor harbor]# vim /etc/docker/daemon.json
[root@harbor harbor]# cat /etc/docker/daemon.json
{"registry-mirrors": ["https://9upbt3ho.mirror.aliyuncs.com"],"insecure-registries": ["192.168.200.53"]
}
[root@harbor harbor]# systemctl daemon-reload
[root@harbor harbor]# systemctl restart docker
1.4.6 Containerd 配置 insecure registry (k8s节点)
如果 Kubernetes 集群采用的是 Containerd 作为的 Runtime,那么 Containerd 也需要配置 insecure registry。
首先生成 Containerd 的配置(如果已经生成过,请勿执行):
# containerd config default > /etc/containerd/config.toml
# 修改配置(所有k8s节点都要执行)
[root@k8s-master01 ~]# vim /etc/containerd/config.toml
[root@k8s-master01 ~]# sed -n "170,172p" /etc/containerd/config.toml [plugins."io.containerd.grpc.v1.cri".registry.mirrors][plugins."io.containerd.grpc.v1.cri".registry.mirrors."192.168.200.53"]endpoint = ["http://192.168.200.53"]# 重启服务
[root@k8s-master01 ~]# systemctl restart containerd# 需要把K8s所有的节点都需要进行更改
# config.toml并不是直接给ctr命令去使用的
# docker拉取的镜像和containerd没有任何关系
1.5 Jenkins 安装
1.5.1 安装 Docker
# 添加docker的yum源配置文件
[root@jenkins ~]# yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo# 安装docker
[root@jenkins ~]# yum install docker-ce docker-ce-cli -y# 启动docker
[root@jenkins ~]# systemctl daemon-reload && systemctl enable --now docker
1.5.2 安装Jenkins
创建 Jenkins 的数据目录,防止容器重启后数据丢失:
[root@jenkins ~]# mkdir /data/jenkins_data -p
[root@jenkins ~]# chmod -R 777 /data/jenkins_data
启动 Jenkins,并配置管理员账号密码为 admin/admin123:
# 拉取镜像
[root@jenkins ~]# docker pull crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/jenkins:2.504.3-debian-12-r1# 启动容器
[root@jenkins ~]# docker run -d --name=jenkins --restart=always -e JENKINS_PASSWORD=admin123 -e JENKINS_USERNAME=admin -e JENKINS_HTTP_PORT_NUMBER=8080 -p 8080:8080 -p 50000:50000 -v /usr/bin/docker:/usr/bin/docker -v /var/run/docker.sock:/var/run/docker.sock -v /data/jenkins_data:/bitnami/jenkins crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/jenkins:2.504.3-debian-12-r1# 查看容器
[root@jenkins ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8e44a0ba9584 crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/jenkins:2.504.3-debian-12-r1 "/opt/bitnami/script…" 2 minutes ago Up 2 minutes 0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp, 0.0.0.0:50000->50000/tcp, [::]:50000->50000/tcp, 8443/tcp jenkins
其中 8080 端口为 Jenkins Web 界面的端口,50000 是 jnlp 使用的端口,后期 Jenkins Slave 需要使用 50000 端口和 Jenkins 主节点通信。
查看 Jenkins 日志:
# 查看到这条日志说明 Jenkins 已完成启动
[root@jenkins ~]# docker logs -f jenkins
....
INFO hudson.lifecycle.Lifecycle#onReady: Jenkins is fully up and running
之后通过 Jenkins 宿主机的 IP+8080 即可访问 Jenkins
1.5.3 插件安装
登录后点击 Manage Jenkins → Manage Plugins 安装需要使用的插件:
在安装之前首先配置国内的插件源,点击 Advanced,将插件源更改为国内插件源(https://mirrors.huaweicloud.com/jenkins/updates/update-center.json)
点击 Submit 后在 Available 可以看到所有的可用插件:
Git
Git Parameter
Git Pipeline for Blue Ocean
GitLab
Credentials
Credentials Binding
Blue Ocean
Blue Ocean Pipeline Editor
Blue Ocean Core JS
Web for Blue Ocean
Pipeline SCM API for Blue Ocean
Dashboard for Blue Ocean
Build With Parameters
List Git Branches Parameter
Pipeline
Pipeline: Declarative
Kubernetes
Kubernetes CLI
Kubernetes Credentials
Image Tag Parameter
Docker
Docker Slaves
Docker Pipeline
Role-based Authorization Strategy
最后安装完记得重启
等待安装后自动重启后,就可以在 Installed 看到已经安装的插件:
至此 Jenkins 和 Jenkins 插件的安装就完成了。
1.5.4 docker配置insecure registry
[root@jenkins ~]# vim /etc/docker/daemon.json
[root@jenkins ~]# cat /etc/docker/daemon.json
{"registry-mirrors": ["https://9upbt3ho.mirror.aliyuncs.com"],"insecure-registries": ["192.168.200.53"]
}
[root@jenkins ~]# systemctl daemon-reload
[root@jenkins ~]# systemctl restart docker
1.6 GitLab 安装
GitLab 在企业内经常用于代码的版本控制,也是 DevOps 平台中尤为重要的一个工具。
1.6.1 关闭机器防火墙和 selinux
# 关闭防火墙
[root@gitlab ~]# systemctl disable --now firewalld
[root@gitlab ~]# systemctl disable --now dnsmasq# 关闭selinux
[root@gitlab ~]# setenforce 0
[root@gitlab ~]# sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/sysconfig/selinux
[root@gitlab ~]# sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config
1.6.2 安装gitlab
[root@gitlab ~]# wget https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el9/gitlab-ce-17.9.8-ce.0.el9.x86_64.rpm
[root@gitlab ~]# yum install gitlab-ce-17.9.8-ce.0.el9.x86_64.rpm -y
# 修改配置
[root@gitlab ~]# vim /etc/gitlab/gitlab.rb
[root@gitlab ~]# sed -n "32p;2346p" /etc/gitlab/gitlab.rb
external_url 'http://192.168.200.55' # 将 external_url 更改为自己的发布地址,可以是服务器的 IP,也可以是一个可被解析的域名
prometheus['enable'] = false # 关闭 Prometheus 插件(可选)# 更改完成后需要重新加载配置文件
[root@gitlab ~]# gitlab-ctl reconfigure
....
Notes:
Default admin account has been configured with following details:
Username: root
Password: You didn't opt-in to print initial root password to STDOUT.
Password stored to /etc/gitlab/initial_root_password. This file will be cleaned up in first reconfigure run after 24 hours.NOTE: Because these credentials might be present in your log files in plain text, it is highly recommended to reset the password following https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password.gitlab Reconfigured!
之后可以通过 浏览器访 问 GitLab,账号 root,默认密码在/etc/gitlab/initial_root_password
:
登录后,开启 import 功能,可以从外部仓库导入代码到 Gitlab:
最后记得点击保存
1.6.3 创建项目
1、创建一个测试项目,进行一些简单的测试。首先创建一个组:
组名为 kubernetes,类型为 Private,之后点击 Create group 即可:
之后在该组下创建一个 Project:
选择创建一个空的项目:
输入项目名称,然后点击 Create project 即可:
1.6.4 配置密钥
之后可以将 k8s-master 服务器(任意机器均可)的 key 导入到 GitLab
# 首先生成密钥(如有可以无需生成):
[root@k8s-master01 ~]# ssh-keygen -t rsa -C "1773464408@qq.com"# 将公钥的内容放在 GitLab 中即可:
[root@k8s-master01 ~]# cat .ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDJTvF8NDvX/SKxS1z7j/SdqInCB2A3kfuRsOn3c/j962H+yN/0SfRXShaSpHzvrVPNQMtiEr4gFQ4UKgpcWUyyDeaCyYRDrUxZ96IDoY2KoTser5o9dtfmnYaKeP7koewINFesPj59Ur2pATks7SYoT59o7UGKrShcedgo23dI0pvJmNaYKOlLeYocbaCY31g76SCZ44jVmv92smU9L+rVS15FT6hTjMO194vflbhLQ5p5nyaWRORYHuHxTbe5YHpMmEQquLnvMvhAr9ngzCdeMNyVtpQx2ZcbR9YmB6WiL6VsGqyPywkb2rU9G4I0qW8bg+aI1tTnjlXCEGKCNV7rxqNx3h0vRADTlYpKeeh1Ysrt+5+CibYJj3KNfY1dXLFOCCDc6YIFRX/0jFk/7RHe2r7knsqajc1ytt0BdDYb6GBeckDhIL4WGiKI3LtHI4LdXammTAf9wwIv7ZEEyuzAHd71AKx/JTKcA+jmGGy64j2i8Xzpk9UyWuhhxOwwJKc= 1773464408@qq.com
在 GitLab 找到 Profile:
之后在 SSH Keys 添加公钥:
添加后就可以在服务器上拉取代码:
[root@k8s-master01 ~]# yum install git -y
[root@k8s-master01 ~]# git config --global user.email 1773464408@qq.com
[root@k8s-master01 ~]# git config --global user.name "yunwei"
[root@k8s-master01 ~]# git clone git@192.168.200.55:kubernetes/test.git
1.7 Jenkins 凭证 Credentials
Harbor 的账号密码、Kubernetes 的证书、GitLab 的私钥均使用 Jenkins 的 Credentials 管理。
1.7.1 配置 Harbor 账号密码
首先点击 Manage Jenkins,之后点击 Credentials:
- Username:Harbor 或者其它平台的用户名
- Password:Harbor 或者其它平台的密码
- ID:该凭证的 ID
- Description:证书的描述
1.7.2 配置 Kubernetes 证书
首先需要找到集群中的 KUBECONFIG,一般是 kubectl 节点的~/.kube/config
文件,或者是 KUBECONFIG 环境变量
所指向的文件。
接下来只需要把证书文件放置于 Jenkins 的 Credentials 中即可,点击 Add Credentials,类型选择为 Secret file
:
1.7.3 配置 GitLab Key
前面将k8s的公钥放到了gitlab上面,可以拉取上面的代码;现在把k8s的私钥放到jenkin上面,现在jenkins也就可以拉取gitlab上面的代码
# 私钥查询
[root@k8s-master01 ~]# cat .ssh/id_rsa
点击 Add Credentials,类型选择为 SSH Username with private key
:
至此我们所有的凭证都添加好了
1.8 配置 Agent
通常情况下,Jenkins Slave 会通过 Jenkins Master 节点的 50000 端口与之通信,所以需要开启 Agent 的 50000 端口。
点击 Manage Jenkins,然后点击 Security:
在安全配置下方找到 Agents,点击 Fixed,输入 50000 即可:
实际使用时,如果不需要把整个 Kubernetes 集群的节点都充当创建 Jenkins Slave Pod 的节点,可以选择任意的一个或多个节点作为创建 Slave Pod 的节点。
假设 k8s-node01 作为 Slave 节点(可选):
[root@k8s-master01 ~]# kubectl label node k8s-node01 build=true
1.9 Jenkins 配置 Kubernetes 多集群
首先点击 Manage Jenkins,之后点击 Clouds:
点击 New cloud:
之后输入集群的名称,一般按照可识别的名称即可,比如现在是学习环境的 Kubernetes 可以叫做kubernetes-study
,选择 Kubernetes,之后点击 Create:
在 Credentials(凭据)处选择之前添加 Kubernetes 证书,选择后点击 Test Connection,最后在凭证下方即可看到能否正常连接的结果:
最后点击 Save 即可,添加完 Kubernetes 后,在 Jenkinsfile 的 Agent 中,就可以选择该集群作为创建 Slave 的集群。
如果想要添加多个集群,重复上述的步骤即可。
2、自动化流水线设计
2.1 自动化流水线统一模板
针对如上的逻辑,可以设计一个流水线模板,只需要变更一些参数即可。
首先是顶层的 Agent,定义的是 Kubernetes 的 Pod 作为 Jenkins 的 Slave:
agent {kubernetes { # 定义使用 Kubernetes 作为 agentcloud 'kubernetes-study'slaveConnectTimeout 1200# 将 workspace 改成 PVC,用于持久化工作目录,claimName 为创建的 PVC 名称workspaceVolume persistentVolumeClaimWorkspaceVolume(claimName: "pipeline-cache", readOnly: false)yaml '''
apiVersion: v1
kind: Pod
spec:restartPolicy: "Never"nodeSelector: # 固定节点部署,可选build: "true"securityContext: {}volumes:- name: docker-registry-config # docker 认证信息 volumeconfigMap:name: docker-registry-config- name: "localtime"hostPath:path: "/usr/share/zoneinfo/Asia/Shanghai"- name: cache # 构建缓存 PVCpersistentVolumeClaim:claimName: cachereadonly: falsecontainers:# jnlp 容器,和 Jenkins 主节点通信- name: jnlpimage: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/jnlp-agent-docker:latest"imagePullPolicy: IfNotPresentargs: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']volumeMounts:- name: "localtime"mountPath: "/etc/localtime"readOnly: false# build 容器,包含执行构建的命令,比如 Java 的需要 mvn 构建,就可以用一个 maven 的镜像# 容器的名字,流水线的 stage 可以直接使用该名字- name: "build"# 使用 Maven 镜像,包含 mvn 工具,NodeJS 可以用 node 的镜像,根据实际情况修改image: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/maven:3.5.3"imagePullPolicy: "IfNotPresent"command:- "cat"env:- name: "LANGUAGE"value: "en_US:en"- name: "LC_ALL"value: "en_US.UTF-8"- name: "LANG"value: "en_US.UTF-8"tty: truevolumeMounts:- name: "localtime"mountPath: "/etc/localtime"# 由于 Java 编译时,会把依赖的插件缓存到~/.m2目录,所以把该目录也进行缓存,如果其他语言的编译也需要缓存,按照该方式配置即可- name: "cache"mountPath: "/root/.m2/"readOnly: false# 用于生成镜像的容器,可以是 Docker 或者是其他的可以用于构建镜像的工具- name: "kaniko"# 本示例采用 Kaniko 制作镜像image: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/kaniko-executor:debug"imagePullPolicy: "IfNotPresent"command:- "sleep"args:- "99d"env:- name: "LANGUAGE"value: "en_US:en"- name: "LC_ALL"value: "en_US.UTF-8"- name: "LANG"value: "en_US.UTF-8"tty: truevolumeMounts:- name: "localtime"mountPath: "/etc/localtime"readOnly: false# 挂载 docker 配置文件,用于 Kaniko 链接镜像仓库- name: "docker-registry-config"mountPath: "/kaniko/.docker"# 发版容器,因为最终是发版至 Kubernetes 的,所以需要有一个 kubectl 命令或者是其他的发版工具也可以,比如 helm- name: "kubectl"# 发版的镜像根据实际需求进行变更image: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/kubectl:devops"imagePullPolicy: "IfNotPresent"command:- "cat"env:- name: "LANGUAGE"value: "en_US:en"- name: "LC_ALL"value: "en_US.UTF-8"- name: "LANG"value: "en_US.UTF-8"tty: truevolumeMounts:- name: "localtime"mountPath: "/etc/localtime"readOnly: false'''}
之后看一下 Jenkinsfile 最后的环境变量和 parameters 的配置:
# 定义一些全局的环境变量environment {COMMIT_ID = ""HARBOR_ADDRESS = "192.168.200.53" # Harbor 地址REGISTRY_DIR = "kubernetes" # Harbor 的项目目录IMAGE_NAME = "spring-boot-project" # 镜像的名称NAMESPACE = "kubernetes" # 该应用在 Kubernetes 中的命名空间TAG = "" # 镜像的 Tag,在此用 BUILD_TAG+COMMIT_ID 组成GIT_URL = "git@192.168.200.55:kubernetes/spring-boot-project.git" # 代码地址}parameters {# 之前讲过一些 choice、input 类型的参数,本次使用的是 GitParameter 插件# 该字段会在 Jenkins 页面生成一个选择分支的选项gitParameter(branch: '', branchFilter: 'origin/(.*)', defaultValue: '', description: 'Branch for build and deploy', name: 'BRANCH', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH')}
接下来是拉代码的 stage,这个 stage 是一个并行的 stage,因为考虑了该流水线是手动触发还是触发:
stage('Pulling Code') {parallel {stage('Pulling Code by Jenkins') {when {expression {# 假如 env.gitlabBranch 为空,则该流水线为手动触发,那么就会执行该 stage,如果不为空则会执行同级的另外一个 stageenv.gitlabBranch == null}}steps {# 这里使用的是 git 插件拉取代码,BRANCH 变量取自于前面介绍的 parameters 配置# git@xxxxxx:root/spring-boot-project.git 代码地址# credentialsId: 'gitlab-key',之前创建的拉取代码的 keygit(changelog: true, poll: true, url: "${GIT_URL}", branch: "${BRANCH}", credentialsId: 'gitlab-key')script {# 定义一些变量用于生成镜像的 Tag# 获取最近一次提交的 Commit IDCOMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()# 赋值给 TAG 变量,后面的 docker build 可以取到该 TAG 的值TAG = BUILD_TAG + '-' + COMMIT_IDprintln "Current branch is ${BRANCH}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"}}}stage('Pulling Code by trigger') {when {expression {# 如果 env.gitlabBranch 不为空,说明该流水线是通过 webhook 触发,则此时执行该 stage,上述的 stage 不再执行。此时 BRANCH 变量为空env.gitlabBranch != null}}steps {# 以下配置和上述一致,只是此时 branch: env.gitlabBranch 取的值为 env.gitlabBranchgit(url: "${GIT_URL}", branch: env.gitlabBranch, changelog: true, poll: true, credentialsId: 'gitlab-key')script {COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()TAG = BUILD_TAG + '-' + COMMIT_IDprintln "Current branch is ${env.gitlabBranch}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"}}}}}
代码拉下来后,就可以执行构建命令,由于本次实验是 Java 示例,所以需要使用 mvn 命令进行构建:
stage('Building') {steps {container(name: 'build') { # 使用 Pod 模板里面的 build 容器进行构建sh """ # 编译命令,需要根据自己项目的实际情况进行修改,可能会不一致curl repo.maven.apache.orgmvn clean install -DskipTestsls target/*"""}}}
生成编译产物后,需要根据该产物生成对应的镜像,此时可以使用 Pod 模板的 kaniko 容器:
stage('Build for creating image') {steps {container(name: 'kaniko') { # 指定使用 kaniko 容器sh """# 执行 build 命令,Dockerfile 会在下一小节创建,也是放在代码仓库,和 Jenkinsfile 同级executor -d ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -c . --insecure --skip-tls-verify"""}}}
最后一步就是将该镜像发版至 Kubernetes 集群中,此时使用的是包含 kubectl 命令的容器:
stage('Deploying to K8s') {environment { # 获取连接 Kubernetes 集群证书MY_KUBECONFIG = credentials('STUDY_CLUSTER_CONFIG')}steps {container(name: 'kubectl'){ # 指定使用 kubectl 容器sh """ # 直接 set 更改 Deployment 的镜像即可kubectl --kubeconfig $MY_KUBECONFIG set image deploy -l app=${IMAGE_NAME} ${IMAGE_NAME}=${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -n $NAMESPACE"""}}}}
2.2 Workspace 持久化
Jenkins 在构建时,会产生一些依赖文件,这些文件最好进行持久化存储,防止重复下载。接下来创建一个 PVC 用于流水线工作目录及依赖文件的数据持久化:
[root@k8s-master01 ~]# vim pipeline-cache-pvc.yaml
[root@k8s-master01 ~]# cat pipeline-cache-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: pipeline-cachenamespace: default
spec:resources:requests:storage: 200GivolumeMode: FilesystemstorageClassName: nfs-csiaccessModes:- ReadWriteMany
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: cachenamespace: default
spec:resources:requests:storage: 200GivolumeMode: FilesystemstorageClassName: nfs-csiaccessModes:- ReadWriteMany
创建后查看 PVC:
[root@k8s-master01 ~]# kubectl create -f pipeline-cache-pvc.yaml
[root@k8s-master01 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
cache Bound pvc-5a714f8e-3d1e-432b-8152-4f734a3c95c8 200Gi RWX nfs-csi <unset> 74s
pipeline-cache Bound pvc-375e62f3-463c-449d-87e3-f215b37aedae 200Gi RWX nfs-csi <unset> 74s
注意此时需要在 NFS 把该 PVC 设置为 777 权限:
[root@habor ~]# chmod -R 777 /data/nfs/pvc-*
2.3 Kaniko 配置文件处理
首先登录需要推送镜像的目标仓库,生成 docker 的配置文件:
[root@k8s-master01 ~]# docker login 192.168.200.53
查看生成的配置文件:
[root@k8s-master01 ~]# cat ~/.docker/config.json
{"auths": {"192.168.200.53": {"auth": "YWRtaW46SGFyYm9yMTIzNDU="}}
}
创建 ConfigMap,用于挂载至 Jenkins Slave 的 Kaniko 容器:
[root@k8s-master01 ~]# kubectl create cm docker-registry-config --from-file=config.json=/root/.docker/config.json[root@k8s-master01 ~]# kubectl get cm docker-registry-config
NAME DATA AGE
docker-registry-config 1 15s
3、自动化构建 Java 应用
3.1 创建 Java 测试用例
示例项目可以从 https://gitee.com/dukuan/spring-boot-project.git 找到该项目(也可以使用公司的 Java 项目也是一样的)。
接下来将该项目导入到自己的 GitLab 中。首先找到之前创建的 Kubernetes 组
然后点击 New Project:
选择 Import Project:
点击 Repo by URL,在 Git repository URL 输入示例地址,然后点击 Create Project 即可:
导入后,如下所示:
3.2 定义 Jenkinsfile
Jenkinsfile 是用来保存 Pipeline 代码的文件,通常用代码仓库管理,或者直接放置于服务的代码仓库中。
Jenkinsfile 放置于代码仓库中,有以下好处:
- 方便对流水线上的代码进行复查/迭代;
- 对管道进行审计跟踪;
- 流水线真正的源代码能够被项目的多个成员查看和编辑。
接下来再 GitLab 的源代码中添加 Jenkinsfile。首先点击代码首页的“+”号,然后点击 New file:
在窗口中,添加通过模板更改后的 Pipeline,并且命名为 Jenkinsfile
:
完整配置
pipeline {agent {kubernetes {cloud 'kubernetes-study'slaveConnectTimeout 1200workspaceVolume persistentVolumeClaimWorkspaceVolume(claimName: "pipeline-cache", readOnly: false)yaml '''
apiVersion: v1
kind: Pod
spec:restartPolicy: "Never"nodeSelector:build: "true"securityContext: {}volumes:- name: docker-registry-configconfigMap:name: docker-registry-config- name: "localtime"hostPath:path: "/usr/share/zoneinfo/Asia/Shanghai"- name: cachepersistentVolumeClaim:claimName: cachereadonly: falsecontainers:- name: jnlpimage: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/jnlp-agent-docker:latest"imagePullPolicy: IfNotPresentargs: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']volumeMounts:- name: "localtime"mountPath: "/etc/localtime"readOnly: false- name: "build"image: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/maven:3.5.3"imagePullPolicy: "IfNotPresent"command:- "cat"env:- name: "LANGUAGE"value: "en_US:en"- name: "LC_ALL"value: "en_US.UTF-8"- name: "LANG"value: "en_US.UTF-8"tty: truevolumeMounts:- name: "localtime"mountPath: "/etc/localtime"- name: "cache"mountPath: "/root/.m2/"readOnly: false- name: "kaniko"image: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/kaniko-executor:debug"imagePullPolicy: "IfNotPresent"command:- "sleep"args:- "99d"env:- name: "LANGUAGE"value: "en_US:en"- name: "LC_ALL"value: "en_US.UTF-8"- name: "LANG"value: "en_US.UTF-8"tty: truevolumeMounts:- name: "localtime"mountPath: "/etc/localtime"readOnly: false- name: "docker-registry-config"mountPath: "/kaniko/.docker"- name: "kubectl"image: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/kubectl:devops"imagePullPolicy: "IfNotPresent"command:- "cat"env:- name: "LANGUAGE"value: "en_US:en"- name: "LC_ALL"value: "en_US.UTF-8"- name: "LANG"value: "en_US.UTF-8"tty: truevolumeMounts:- name: "localtime"mountPath: "/etc/localtime"readOnly: false'''}}stages {stage('Pulling Code') {parallel {stage('Pulling Code by Jenkins') {when {expression {env.gitlabBranch == null}}steps {git(changelog: true, poll: true, url: "${GIT_URL}", branch: "${BRANCH}", credentialsId: 'gitlab-key')script {COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()TAG = BUILD_TAG + '-' + COMMIT_IDprintln "Current branch is ${BRANCH}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"}}}stage('Pulling Code by trigger') {when {expression {env.gitlabBranch != null}}steps {git(url: "${GIT_URL}", branch: env.gitlabBranch, changelog: true, poll: true, credentialsId: 'gitlab-key')script {COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()TAG = BUILD_TAG + '-' + COMMIT_IDprintln "Current branch is ${env.gitlabBranch}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"}}}}}stage('Building') {steps {container(name: 'build') {sh """curl repo.maven.apache.orgmvn clean install -DskipTestsls target/*"""}}}stage('Build for creating image') {steps {container(name: 'kaniko') {sh """executor -d ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -c . --insecure --skip-tls-verify"""}}}stage('Deploying to K8s') {environment {MY_KUBECONFIG = credentials('STUDY_CLUSTER_CONFIG')}steps {container(name: 'kubectl'){sh """kubectl --kubeconfig $MY_KUBECONFIG set image deploy -l app=${IMAGE_NAME} ${IMAGE_NAME}=${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -n $NAMESPACE"""}}}}environment {COMMIT_ID = ""HARBOR_ADDRESS = "192.168.200.53"REGISTRY_DIR = "kubernetes"IMAGE_NAME = "spring-boot-project"NAMESPACE = "kubernetes"TAG = ""GIT_URL = "git@192.168.200.55:kubernetes/spring-boot-project.git"}parameters {gitParameter(branch: '', branchFilter: 'origin/(.*)', defaultValue: '', description: 'Branch for build and deploy', name: 'BRANCH', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH')}
}
3.3 定义 Dockerfile
在执行流水线过程时,需要将代码的编译产物做成镜像。而本次示例是 Java 项目,只需要把 Jar 包放在有 Jre 环境的镜像中,然后启动该 Jar 包即可:
# 基础镜像可以按需修改,可以更改为公司自有镜像
FROM crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/jre:8u211-data
# jar 包名称改成实际的名称,本示例为 spring-cloud-eureka-0.0.1-SNAPSHOT.jar
COPY target/spring-cloud-eureka-0.0.1-SNAPSHOT.jar ./
# 启动 Jar 包
CMD java -jar spring-cloud-eureka-0.0.1-SNAPSHOT.jar
3.4 定义 Kubernetes 资源
本示例在 GitLab 创建的 Group 为 kubernetes,可以将其认为是一个项目,同一个项目可以部署至 Kubernetes 集群中同一个 Namespace 中,本示例为 kubernetes 命名空间。
由于使用的是私有仓库,因此也需要先配置拉取私有仓库镜像的密钥:
[root@k8s-master01 ~]# kubectl create ns kubernetes
[root@k8s-master01 ~]# kubectl create secret docker-registry harborkey --docker-server=192.168.200.53 --docker-username=admin --docker-password=Harbor12345 --docker-email=1773464408@qq.com -n kubernetes
[root@k8s-master01 ~]# vim spring-boot-project.yaml
[root@k8s-master01 ~]# cat spring-boot-project.yaml
apiVersion: v1
kind: Service
metadata:creationTimestamp: nulllabels:app: spring-boot-projectname: spring-boot-projectnamespace: kubernetes
spec:ports: # 端口按照实际情况进行修改- name: webport: 8761protocol: TCPtargetPort: 8761selector:app: spring-boot-projectsessionAffinity: Nonetype: ClusterIP
status:loadBalancer: {}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:creationTimestamp: nullname: spring-boot-projectnamespace: kubernetes
spec:ingressClassName: nginxrules:- host: spring-boot-project.test.comhttp:paths:- backend:service:name: spring-boot-projectport:number: 8761path: /pathType: ImplementationSpecific
status:loadBalancer: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:creationTimestamp: nulllabels:app: spring-boot-project # Deployment 标签,和流水线的 set -l 一致name: spring-boot-project # Deployment 名称namespace: kubernetes
spec:replicas: 1selector:matchLabels:app: spring-boot-project # Pod 的标签strategy:rollingUpdate:maxSurge: 1maxUnavailable: 0type: RollingUpdatetemplate:metadata:creationTimestamp: nulllabels:app: spring-boot-project # Pod 的标签spec:affinity:podAntiAffinity:preferredDuringSchedulingIgnoredDuringExecution:- podAffinityTerm:labelSelector:matchExpressions:- key: appoperator: Invalues:- spring-boot-projecttopologyKey: kubernetes.io/hostnameweight: 100containers:- env:- name: TZvalue: Asia/Shanghai- name: LANGvalue: C.UTF-8image: nginx # 此处使用的 nginx 作为原始的镜像,通过 Jenkins 构建并发版后,变成 Java 应用的镜像imagePullPolicy: IfNotPresentlifecycle: {}livenessProbe:failureThreshold: 2initialDelaySeconds: 30periodSeconds: 10successThreshold: 1tcpSocket:port: 8761 # 端口号和健康检查按照实际情况进行修改timeoutSeconds: 2name: spring-boot-project # 容器的名称,需要和流水线 set 命令的容器名称一致ports:- containerPort: 8761 # 端口号按照实际情况进行修改name: webprotocol: TCPreadinessProbe:failureThreshold: 2initialDelaySeconds: 30periodSeconds: 10successThreshold: 1tcpSocket:port: 8761 # 端口号和健康检查按照实际情况进行修改timeoutSeconds: 2resources: # 资源请求按照实际情况修改limits:cpu: 994mmemory: 1170Mirequests:cpu: 10mmemory: 55MidnsPolicy: ClusterFirstimagePullSecrets:- name: harborkey # Harbor 仓库密钥,需要和上述创建的 Secret 一致restartPolicy: AlwayssecurityContext: {}serviceAccountName: default
创建该资源 (Pod 无法启动请忽略):
[root@k8s-master01 ~]# kubectl create -f spring-boot-project.yaml
[root@k8s-master01 ~]# kubectl get -f spring-boot-project.yaml
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/spring-boot-project ClusterIP 10.107.107.79 <none> 8761/TCP 11sNAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/spring-boot-project <none> spring-boot-project.test.com 80 11sNAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/spring-boot-project 0/1 1 0 10s
3.5 创建 Jenkins 任务(Job)
1、单击首页的创建任务(Job)选项(或者是 New Item)并配置任务信息,如图所示:
2、输入的 Job 的名称(一般和 GitLab 的仓库名字一致,便于区分),类型为 Pipeline,最后点击 OK 即可:
3、如果有如下报错,关闭 Host Key 校验:
去安全里禁用主机密钥验证,直接信任所有连接(尽在内网可信环境中使用)
4、创建完成后,点击 Build Now(由于 Jenkins 参数由 Jenkinsfile 生成,所以第一次执行流水线会失败):
5、第一次构建结束后,可以看到 Build Now 变成了 Build with Parameters。点击 Build with Parameters 后,可以读取到 Git 仓库的分支,之后可以选择分支进行手动构建(后面的章节会介绍自动触发):
6、选择分支,之后点击 Build,然后点击进度条即可看到构建日志:
构建日志上部分为创建 Pod 的日志,可以看到 Pod 为 Agent 指定的 Pod
7、此时在 Kubernetes 中,会创建一个 Jenkins Slave 的 Pod:
[root@k8s-master01 ~]# kubectl get po
NAME READY STATUS RESTARTS AGE
spring-boot-project-2-h155p-d68tz-jfbf2 0/4 ContainerCreating 0 84s
8、待 Pod 启动后,Pipeline 任务就会执行,同时也可以点击 Blue Ocean 更加直观的流程:
9、如果是 Java 应用,同时可以看到 mvn 编译的过程:
编译结束后,可以看到制作镜像的日志:
最后为发版至 Kubernetes:
10、Finished:SUCCESS 说明该流水线正常结束。接下来可以查看 Deployment 的镜像:
[root@k8s-master01 ~]# kubectl get deploy -n kubernetes spring-boot-project -oyaml | grep "image:"image: 192.168.200.53/kubernetes/spring-boot-project:jenkins-spring-boot-project-2-3ed977e[root@k8s-master01 ~]# kubectl get po -n kubernetes
NAME READY STATUS RESTARTS AGE
spring-boot-project-65b5fb956d-qrlc4 1/1 Running 0 13m
12、如果配置了域名,可以通过 域名访问(或者通过 Service 访问):
4、 自动化构建 Vue/H5 前端应用
本节介绍自动化构建 Vue/H5 应用,其构建方式和自动化构建 Java 基本相同,重点是更改 Deployment、Jenkinsfile 和 Dockerfile 即可。
前端应用测试项目地址:https://gitee.com/dukuan/vue-project.git,可以参考 Java 小节的方式,
导入前端项目到 GitLab 中,当然也可以使用公司自己的项目。
4.1 定义 Jenkinsfile
Jenkinsfile 和 Java 项目并无太大区别,需要更改的位置如下:
# 编译镜像改为 NodeJS
name: "build"
image: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/node:lts"
imagePullPolicy: "IfNotPresent"# 构建命令改为 npm
npm install --registry=https://registry.npmmirror.com/
npm run build# 代码地址和服务名称
IMAGE_NAME = "vue-project"
NAMESPACE = "kubernetes"
TAG = ""
GIT_URL = "git@192.168.200.55:kubernetes/vue-project.git"
添加 Jenkinsfile 至项目根目录
完整配置
pipeline {agent {kubernetes {cloud 'kubernetes-study'slaveConnectTimeout 1200workspaceVolume persistentVolumeClaimWorkspaceVolume(claimName: "pipeline-cache", readOnly: false)yaml '''
apiVersion: v1
kind: Pod
spec:restartPolicy: "Never"nodeSelector:build: "true"securityContext: {}volumes:- name: docker-registry-configconfigMap:name: docker-registry-config- name: "localtime"hostPath:path: "/usr/share/zoneinfo/Asia/Shanghai"- name: cachepersistentVolumeClaim:claimName: cachereadonly: falsecontainers:- name: jnlpimage: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/jnlp-agent-docker:latest"imagePullPolicy: IfNotPresentargs: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']volumeMounts:- name: "localtime"mountPath: "/etc/localtime"readOnly: false- name: "build"image: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/node:lts"imagePullPolicy: "IfNotPresent"command:- "cat"env:- name: "LANGUAGE"value: "en_US:en"- name: "LC_ALL"value: "en_US.UTF-8"- name: "LANG"value: "en_US.UTF-8"tty: truevolumeMounts:- name: "localtime"mountPath: "/etc/localtime"- name: "cache"mountPath: "/root/.m2/"readOnly: false- name: "kaniko"image: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/kaniko-executor:debug"imagePullPolicy: "IfNotPresent"command:- "sleep"args:- "99d"env:- name: "LANGUAGE"value: "en_US:en"- name: "LC_ALL"value: "en_US.UTF-8"- name: "LANG"value: "en_US.UTF-8"tty: truevolumeMounts:- name: "localtime"mountPath: "/etc/localtime"readOnly: false- name: "docker-registry-config"mountPath: "/kaniko/.docker"- name: "kubectl"image: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/kubectl:devops"imagePullPolicy: "IfNotPresent"command:- "cat"env:- name: "LANGUAGE"value: "en_US:en"- name: "LC_ALL"value: "en_US.UTF-8"- name: "LANG"value: "en_US.UTF-8"tty: truevolumeMounts:- name: "localtime"mountPath: "/etc/localtime"readOnly: false'''}}stages {stage('Pulling Code') {parallel {stage('Pulling Code by Jenkins') {when {expression {env.gitlabBranch == null}}steps {git(changelog: true, poll: true, url: "${GIT_URL}", branch: "${BRANCH}", credentialsId: 'gitlab-key')script {COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()TAG = BUILD_TAG + '-' + COMMIT_IDprintln "Current branch is ${BRANCH}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"}}}stage('Pulling Code by trigger') {when {expression {env.gitlabBranch != null}}steps {git(url: "${GIT_URL}", branch: env.gitlabBranch, changelog: true, poll: true, credentialsId: 'gitlab-key')script {COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()TAG = BUILD_TAG + '-' + COMMIT_IDprintln "Current branch is ${env.gitlabBranch}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"}}}}}stage('Building') {steps {container(name: 'build') {sh """npm install --registry=https://registry.npmmirror.com/npm run build"""}}}stage('Build for creating image') {steps {container(name: 'kaniko') {sh """executor -d ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -c . --insecure --skip-tls-verify"""}}}stage('Deploying to K8s') {environment {MY_KUBECONFIG = credentials('STUDY_CLUSTER_CONFIG')}steps {container(name: 'kubectl'){sh """kubectl --kubeconfig $MY_KUBECONFIG set image deploy -l app=${IMAGE_NAME} ${IMAGE_NAME}=${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -n $NAMESPACE"""}}}}environment {COMMIT_ID = ""HARBOR_ADDRESS = "192.168.200.53"REGISTRY_DIR = "kubernetes"IMAGE_NAME = "vue-project"NAMESPACE = "kubernetes"TAG = ""GIT_URL = "git@192.168.200.55:kubernetes/vue-project.git"}parameters {gitParameter(branch: '', branchFilter: 'origin/(.*)', defaultValue: '', description: 'Branch for build and deploy', name: 'BRANCH', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH')}
}
4.2 定义 Dockerfile
前端应用构建后一般会在 dist 文件下产生 html 文件,只需要拷贝到 nginx 的根目录下即可:
FROM crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/nginx:1.15
COPY dist/* /usr/share/nginx/html/
4.3 定义 Kubernetes 资源
对于 Kubernetes 的资源也是类似的,只需要更改资源名称和端口号即可(加粗部分):
[root@k8s-master01 ~]# vim vue-project.yaml
[root@k8s-master01 ~]# cat vue-project.yaml
apiVersion: v1
kind: Service
metadata:creationTimestamp: nulllabels:app: vue-projectname: vue-projectnamespace: kubernetes
spec:ports:- name: webport: 80protocol: TCPtargetPort: 80selector:app: vue-projectsessionAffinity: Nonetype: ClusterIP
status:loadBalancer: {}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:creationTimestamp: nullname: vue-projectnamespace: kubernetes
spec:ingressClassName: nginxrules:- host: vue-project.test.comhttp:paths:- backend:service:name: vue-projectport:number: 80path: /pathType: ImplementationSpecific
status:loadBalancer: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:creationTimestamp: nulllabels:app: vue-projectname: vue-projectnamespace: kubernetes
spec:replicas: 1selector:matchLabels:app: vue-projectstrategy:rollingUpdate:maxSurge: 1maxUnavailable: 0type: RollingUpdatetemplate:metadata:creationTimestamp: nulllabels:app: vue-projectspec:affinity:podAntiAffinity:preferredDuringSchedulingIgnoredDuringExecution:- podAffinityTerm:labelSelector:matchExpressions:- key: appoperator: Invalues:- vue-projecttopologyKey: kubernetes.io/hostnameweight: 100containers:- env:- name: TZvalue: Asia/Shanghai- name: LANGvalue: C.UTF-8image: nginximagePullPolicy: IfNotPresentlifecycle: {}livenessProbe:failureThreshold: 2initialDelaySeconds: 30periodSeconds: 10successThreshold: 1tcpSocket:port: 80timeoutSeconds: 2name: vue-projectports:- containerPort: 80name: webprotocol: TCPreadinessProbe:failureThreshold: 2initialDelaySeconds: 30periodSeconds: 10successThreshold: 1tcpSocket:port: 80timeoutSeconds: 2resources:limits:cpu: 994mmemory: 1170Mirequests:cpu: 10mmemory: 55MidnsPolicy: ClusterFirstimagePullSecrets:- name: harborkeyrestartPolicy: AlwayssecurityContext: {}serviceAccountName: default
创建该资源 (Pod 无法启动请忽略):
[root@k8s-master01 ~]# kubectl create -f vue-project.yaml
[root@k8s-master01 ~]# kubectl get -f vue-project.yaml
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/vue-project ClusterIP 10.96.136.68 <none> 80/TCP 6sNAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/vue-project <none> vue-project.test.com 80 5sNAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/vue-project 0/1 1 0 5s
4.4 创建 Jenkins Job
创建和构建 Job 和 Java 步骤类似,只需要变更名称和仓库地址即可,并且可以通过之前的 Job 进行复制:
执行构建:
构建成功如下所示:
发布成功后可以在浏览器 访问该域名(或者使用 Service 访问):
5、自动化构建 Golang 项目
上述演示了 Java 和前端应用的自动化,接下来演示一下对于 Golang 的自动化构建,本次示例的代码地址:https://gitee.com/dukuan/go-project.git。
5.1 定义 Jenkinsfile
本次示例的 Jenkinsfile 和之前的也无太大区别,需要的更改的位置是构建容器的镜像、缓存目录、Git 地址和项目名称:
# 构建镜像改为 golang,需要根据实际情况更改版本
name: "build"
image: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/golang:1.15"
imagePullPolicy: "IfNotPresent"
...
volumeMounts:
...
- name: "cache"mountPath: "/go/pkg/" # 缓存目录为/go/pkg/,执行 go build 时下载的依赖包会缓存在该目录readOnly: false
# 构建命令
export GO111MODULE=on
export CGO_ENABLED=0
go env -w GOPROXY=https://goproxy.cn,direct
go build# 代码地址和服务名称
IMAGE_NAME = "go-project"
NAMESPACE = "kubernetes"
TAG = ""
GIT_URL = "git@192.168.200.55:kubernetes/go-project.git"
完整文件:
pipeline {agent {kubernetes {cloud 'kubernetes-study'slaveConnectTimeout 1200workspaceVolume persistentVolumeClaimWorkspaceVolume(claimName: "pipeline-cache", readOnly: false)yaml '''
apiVersion: v1
kind: Pod
spec:restartPolicy: "Never"nodeSelector:build: "true"securityContext: {}volumes:- name: docker-registry-configconfigMap:name: docker-registry-config- name: "localtime"hostPath:path: "/usr/share/zoneinfo/Asia/Shanghai"- name: cachepersistentVolumeClaim:claimName: cachereadonly: falsecontainers:- name: jnlpimage: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/jnlp-agent-docker:latest"imagePullPolicy: IfNotPresentargs: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']volumeMounts:- name: "localtime"mountPath: "/etc/localtime"readOnly: false- name: "build"image: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/golang:1.15"imagePullPolicy: "IfNotPresent"command:- "cat"env:- name: "LANGUAGE"value: "en_US:en"- name: "LC_ALL"value: "en_US.UTF-8"- name: "LANG"value: "en_US.UTF-8"tty: truevolumeMounts:- name: "localtime"mountPath: "/etc/localtime"- name: "cache"mountPath: "/go/pkg/"readOnly: false- name: "kaniko"image: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/kaniko-executor:debug"imagePullPolicy: "IfNotPresent"command:- "sleep"args:- "99d"env:- name: "LANGUAGE"value: "en_US:en"- name: "LC_ALL"value: "en_US.UTF-8"- name: "LANG"value: "en_US.UTF-8"tty: truevolumeMounts:- name: "localtime"mountPath: "/etc/localtime"readOnly: false- name: "docker-registry-config"mountPath: "/kaniko/.docker"- name: "kubectl"image: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/kubectl:devops"imagePullPolicy: "IfNotPresent"command:- "cat"env:- name: "LANGUAGE"value: "en_US:en"- name: "LC_ALL"value: "en_US.UTF-8"- name: "LANG"value: "en_US.UTF-8"tty: truevolumeMounts:- name: "localtime"mountPath: "/etc/localtime"readOnly: false'''}}stages {stage('Pulling Code') {parallel {stage('Pulling Code by Jenkins') {when {expression {env.gitlabBranch == null}}steps {git(changelog: true, poll: true, url: "${GIT_URL}", branch: "${BRANCH}", credentialsId: 'gitlab-key')script {COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()TAG = BUILD_TAG + '-' + COMMIT_IDprintln "Current branch is ${BRANCH}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"}}}stage('Pulling Code by trigger') {when {expression {env.gitlabBranch != null}}steps {git(url: "${GIT_URL}", branch: env.gitlabBranch, changelog: true, poll: true, credentialsId: 'gitlab-key')script {COMMIT_ID = sh(returnStdout: true, script: "git log -n 1 --pretty=format:'%h'").trim()TAG = BUILD_TAG + '-' + COMMIT_IDprintln "Current branch is ${env.gitlabBranch}, Commit ID is ${COMMIT_ID}, Image TAG is ${TAG}"}}}}}stage('Building') {steps {container(name: 'build') {sh """export GO111MODULE=onexport CGO_ENABLED=0go env -w GOPROXY=https://goproxy.cn,directgo build"""}}}stage('Build for creating image') {steps {container(name: 'kaniko') {sh """executor -d ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -c . --insecure --skip-tls-verify"""}}}stage('Deploying to K8s') {environment {MY_KUBECONFIG = credentials('STUDY_CLUSTER_CONFIG')}steps {container(name: 'kubectl'){sh """kubectl --kubeconfig $MY_KUBECONFIG set image deploy -l app=${IMAGE_NAME} ${IMAGE_NAME}=${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} -n $NAMESPACE"""}}}}environment {COMMIT_ID = ""HARBOR_ADDRESS = "192.168.200.53"REGISTRY_DIR = "kubernetes"IMAGE_NAME = "go-project"NAMESPACE = "kubernetes"TAG = ""GIT_URL = "git@192.168.200.55:kubernetes/go-project.git"}parameters {gitParameter(branch: '', branchFilter: 'origin/(.*)', defaultValue: '', description: 'Branch for build and deploy', name: 'BRANCH', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH')}
}
5.2 定义 Dockerfile
和之前不一样的地方是,Golang 编译后生成的是一个二进制文件,可以直接执行,所以底层镜像设置为 alpine 或者其它的小镜像即可:
FROM crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/alpine-glibc:alpine-3.9
# 如果定义了单独的配置文件,可能需要拷贝到镜像中
# COPY conf/ ./conf
# 包名按照实际情况进行修改
COPY ./go-project ./
# 启动该应用
ENTRYPOINT [ "./go-project"]
5.3 定义 Kubernetes 资源
[root@k8s-master01 ~]# vim go-project.yaml
[root@k8s-master01 ~]# cat go-project.yaml
apiVersion: v1
kind: Service
metadata:creationTimestamp: nulllabels:app: go-projectname: go-projectnamespace: kubernetes
spec:ports:- name: webport: 8080protocol: TCPtargetPort: 8080selector:app: go-projectsessionAffinity: Nonetype: ClusterIP
status:loadBalancer: {}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:creationTimestamp: nullname: go-projectnamespace: kubernetes
spec:ingressClassName: nginxrules:- host: go-project.test.comhttp:paths:- backend:service:name: go-projectport:number: 8080path: /pathType: ImplementationSpecific
status:loadBalancer: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:creationTimestamp: nulllabels:app: go-projectname: go-projectnamespace: kubernetes
spec:replicas: 1selector:matchLabels:app: go-projectstrategy:rollingUpdate:maxSurge: 1maxUnavailable: 0type: RollingUpdatetemplate:metadata:creationTimestamp: nulllabels:app: go-projectspec:affinity:podAntiAffinity:preferredDuringSchedulingIgnoredDuringExecution:- podAffinityTerm:labelSelector:matchExpressions:- key: appoperator: Invalues:- go-projecttopologyKey: kubernetes.io/hostnameweight: 100containers:- env:- name: TZvalue: Asia/Shanghai- name: LANGvalue: C.UTF-8image: nginximagePullPolicy: IfNotPresentlifecycle: {}livenessProbe:failureThreshold: 2initialDelaySeconds: 30periodSeconds: 10successThreshold: 1tcpSocket:port: 8080timeoutSeconds: 2name: go-projectports:- containerPort: 8080name: webprotocol: TCPreadinessProbe:failureThreshold: 2initialDelaySeconds: 30periodSeconds: 10successThreshold: 1tcpSocket:port: 8080timeoutSeconds: 2resources:limits:cpu: 994mmemory: 1170Mirequests:cpu: 10mmemory: 55MidnsPolicy: ClusterFirstimagePullSecrets:- name: harborkeyrestartPolicy: AlwayssecurityContext: {}serviceAccountName: default
创建该资源 (Pod 无法启动请忽略):
[root@k8s-master01 ~]# kubectl create -f go-project.yaml
[root@k8s-master01 ~]# kubectl get -f go-project.yaml
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/go-project ClusterIP 10.97.238.110 <none> 8080/TCP 8sNAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/go-project <none> go-project.test.com 80 8sNAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/go-project 0/1 1 0 8s
5.4 创建 Jenkins Job
创建和构建 Job 和 Java 步骤类似,只需要变更名称和仓库地址即可,并且可以通过之前的 Job 进行复制:
执行构建
构建成功如下所示:
之后即可 访问该应用(通过 Ingress 和 Service 都可):
6、自动触发构建
之前的构建都是采用手动选择分支进行构建的,实际使用时,项目可能有很多,如果都是手动触发可能比较消耗人力。所以推荐可以按需配置自动触发,即提交代码后自动触发 Jenkins 进行构建任务。
本次用 Go 项目进行演示。首先找到 Go 项目的 Job,点击 Configure
之后勾选 Build when
选择需要触发的分支,并生成触发 token:
最后点击 Save 即可。
接下来配置 GitLab,允许触发外部接口。点击 Gitlab 的 Admin→Settings→Network:
保存后,找到 Go 项目,点击 Settings→WebHooks:
新页面输入点击 Add new webhook
并输入 Jenkins 接口地址和 Token:
确认无误后,点击 Add webhook:
之后下方会添加一个新的 Project Hooks,可以点击 Test 进行 Push 测试:
点击后,即可在 Jenkins 页面看到任务被触发:
也可以通过 Blue Ocean 看到是自动触发的 stage 被执行:
以上就是通过 GitLab 的事件触发 Jenkins 任务,在实际使用时,此功能非常常用,一般会用于开发、测试等环境,省去了手动构建的过程。而在 UAT 和生产环境,一般不需要再次构建,而是选择其它环境产生的镜像进行发版,接下来看一下如何进行不构建进行发版。
7、一次构建多次部署
在企业内部署服务时,往往每个项目都有多个环境,比如 dev、sit、prod 等。但是并非每个环境部署时,都需要进行重新编译、构建镜像等,此时可以把 dev 的镜像直接部署至 sit 和 prod。
创建一个新的 Job,名字为 go-project-uat,类型 Pipeline:
点击页面的 This Project is parameterized(参数化构建):
选择参数类型为 Image Tag Parameter(需要安装 Image Tag 插件),之后定义 Name 为变量的名称,Iamge Name 为 Harbor 的目录和镜像名称:
点击 Advance,输入仓库的地址,注意如果配置了证书,需要配置 https:
点击 Configure,添加 Pipeline 脚本:
pipeline {agent {kubernetes {cloud 'kubernetes-study'slaveConnectTimeout 1200yaml '''
apiVersion: v1
kind: Pod
spec:containers:- name: jnlpimage: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/jnlp-agent-docker:latest"imagePullPolicy: IfNotPresentargs: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']- name: "kubectl"image: "crpi-q1nb2n896zwtcdts.cn-beijing.personal.cr.aliyuncs.com/ywb01/kubectl:devops"imagePullPolicy: "IfNotPresent"command:- "cat"env:- name: "LANGUAGE"value: "en_US:en"- name: "LC_ALL"value: "en_US.UTF-8"- name: "LANG"value: "en_US.UTF-8"tty: truerestartPolicy: "Never" '''}}stages {stage('Deploy') {environment {MY_KUBECONFIG = credentials('STUDY_CLUSTER_CONFIG')}steps {container(name: 'kubectl'){sh """echo ${IMAGE_TAG}kubectl --kubeconfig=${MY_KUBECONFIG} set image deployment -l app=${IMAGE_NAME} ${IMAGE_NAME}=${HARBOR_ADDRESS}/${IMAGE_TAG} -n ${NAMESPACE}kubectl --kubeconfig=${MY_KUBECONFIG} get po -l app=${IMAGE_NAME} -n ${NAMESPACE} -w"""}}}}environment {HARBOR_ADDRESS = "192.168.200.53"IMAGE_NAME = "go-project"NAMESPACE = "kubernetes"TAG = ""}
}
点击 Build with Parameters:
选择一个镜像,点击 Build:
即可看到是直接将镜像版本更新至 Kubernetes,并无构建过程,可以省下很多时间。该流水线也可以选择之前的版本进行回滚操作。
8、集成 Helm 发布
8.1 生成 Helm 模板
虽然上述已经借助流水线实现了服务的自动发版,但是针对 Kubernetes 的资源依旧是提前手动创建,接下来我们借助 Helm Charts 实现微服务资源的自动创建。
首先创建一个 Helm Chart 的模板:
[root@k8s-master01 ~]# helm create chart-template
接下来对模板稍加修改,使其变成一个通用的模板。首先更改默认拉取镜像的 Secret:
[root@k8s-master01 ~]# cd chart-template
[root@k8s-master01 chart-template]# vim values.yaml
[root@k8s-master01 chart-template]# sed -n "19,20p;26p;28p;79,85p;98,103p" values.yaml
imagePullSecrets:
- name: harborkey
serviceAccount:create: false
resources:limits:cpu: 1000mmemory: 512Mirequests:cpu: 100mmemory: 128Mi
livenessProbe:tcpSocket:port: http
readinessProbe:tcpSocket:port: http
8.2 流水线集成 Helm
接下来使用 go-project 为例,生成一个 go-project 的 Chart:
[root@k8s-master01 ~]# git clone git@192.168.200.55:kubernetes/go-project.git
[root@k8s-master01 ~]# cp -rp chart-template/ go-project/chart
把模板上传至该项目的 Git 仓库,首先把代码下载到本地,然后把模板放到代码 chart 目录下:
[root@k8s-master01 ~]# cd go-project
[root@k8s-master01 go-project]# git add .
[root@k8s-master01 go-project]# git commit -am "添加chart模板"
[root@k8s-master01 go-project]# git push origin master
创建一个新的uat环境
[root@k8s-master01 ~]# kubectl create ns kubernetes-uat
[root@k8s-master01 ~]# kubectl create secret docker-registry harborkey --docker-server=192.168.200.53 --docker-username=admin --docker-password=Harbor12345 --docker-email=1773464408@qq.com -n kubernetes-uat
接下来按需修改values模板文件
修改Jenkinsfile模板文件
cd chart
helm upgrade \
--kubeconfig $MY_KUBECONFIG \
--install $IMAGE_NAME . \
--namespace ${NAMESPACE} \
--create-namespace \
--set fullnameOverride=$IMAGE_NAME \
--set nameOverride=$IMAGE_NAME \
--set replicaCount=1 \
--set image.repository=${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME} \
--set image.tag=${TAG}
提交代码后,重新发版:
自动触发构建完成发布
查看资源:
[root@k8s-master01 ~]# kubectl get po -n kubernetes-uat
NAME READY STATUS RESTARTS AGE
go-project-6c7bd8d985-qx7mv 1/1 Running 0 45s[root@k8s-master01 ~]# helm list -n kubernetes-uat
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
go-project kubernetes-uat 1 2025-09-24 00:06:45.503610996 +0800 CST deployed go-project-0.1.0 1.16.0
此博客来源于:https://edu.51cto.com/lecturer/11062970.html