手把手教你用 Jenkins + K8S 打造流水线环境

朋友的真实操作流程,使用 Jenkins 和 Kubernetes 完成持续集成和持续部署,有搭建,有入门,手把手教学文档,干得拧不出水来,分享一波。

本文作者:孙丹丹,单身 ,DevOps 运维工程师,CKA 认证。就职于某容器云平台服务公司,负责国内多家知名企业 DevOps 运维交付。

安装 Jenkins

启动 Jenkins 容器

docker run -d -u root -v /workspace/jenkins-home/var/jenkins_home  -v /var/run/docker.sock:/var/run/docker.sock -v "$HOME":/home -p 8080:8080 -p 50000:50000 jenkinsci/blueocean
参数 说明
-d d 指 daemon,后台启动
-u 指定运行用户
-v -v /workspace/jenkins-home:/var/jenkins_home
hostDir:containerDir
表示将容器中 jenkins_home 映射到宿主机 jenkins-home 目录
-p -p 8080:8080
hostPort:containerPort

检查 Jenkins 服务状态

docker ps | grep jenkins

启动成功后,就可以通过 ip:port 在浏览器上访问了。ip 为 docker 所在机器的 ip, port 为 Jenkins 容器映射的宿主机端口。

配置 Jenkins

Jenkins 启动成功后,需要执行一些快速的 "一次性" 步骤。当你第一次访问一个新的 Jenkins 实例时, 要求你使用自动生成的密码对其进行解锁。密码为 Jenkins 所在容器的 /var/jenkins_home/secrets/initialAdminPassword 的内容:

docker exec -it <jenkins_container> bash -c "cat /var/jenkins_home/secrets/initialAdminPassword"

或者用 K8S 命令 kubectl

kubectl exec -it <jenkins_container> bash -c "cat /var/jenkins_home/secrets/initialAdminPassword"

在 Unlock Jenkins 页面, 粘贴该密码到 Administrator password 字段并点击 Continue。解锁 Jenkins 后,插件安装页面出现,点击 Install suggested plugins 即可。

这个过程会耗时一段时间。

创建第一个管理员用户,可以填写,也可以跳过,直接使用 admin 账户继续。

这边我选择了跳过,点击开始使用 Jenkins,就可以使用 Jenkins 了。

实现 Java 应用持续集成和持续发布

Fork 和 Clone GitHub 示例仓库

  1. 登录 GitHub 账号:https://github.com
  2. 上传代码到 GitHub 代码仓库中,或 Fork 到你的 GitHub 仓库中,演示代码 prometheus-test-demo,地址为 https://github.com/0820sdd/prometheus-test-demo
  3. 将你的 GitHub 账户中的 prometheus-test-demo 仓库 Clone 到本地机器:
    a. 打开一个终端/命令提示符,并且进入正确的目录路径: Mac OS 系统路径为 /Users/<your-username>/Documents/GitHub/ Linux 系统路径为/home/<your-username>/GitHub/ Windows 系统路径为 C:\Users\<your-username>\Documents\GitHub\ (推荐使用 Git bash 命令行,而不是通常的 Microsoft 命令提示符)
    b. 运行以下命令完成仓库的 clone:git clone https://github.com/YOUR-GITHUB-ACCOUNT-NAME/simple-java-maven-app 其中 YOUR-GITHUB-ACCOUNT-NAME 是你的 GitHub 账户的名称。

在 Jenkins 中创建流水线

登录 Jenkins,点击页面创建一个新任务。若是你无法看见该内容,点击左上方的新建 item。

为新建的流水线项目指定名称(例如 prometheus-test-demo),选择流水线,点击确定。

安装 K8S 插件

登录 Jenkins,系统管理→ 插件管理 → 搜索 kubernetes,选择第二个 Kubernetes,点击 安装,安装完成后重启 Jenkins 。

对接 K8S 集群

申请 K8S 凭据

因为 Jenkins 服务器在 kubernetes 集群之外,所以我们准备以下文件才能从外面连接到 kubernetes 集群。

登录 Jenkins,点击右上角「用户」 → 左下角「凭据」:

然后点击 Jenkins,选择全局凭据(Unrestricted)

添加凭据,类型选择 X.509 Client Certificate

  • Client Key: .kube/config文件中 client-key 对应的 key 文件
  • Client Certificate: .kube/config文件中 client-certificate 对应的 crt 或是 pem 文件
  • Server CA Certificate:.kube/config 文件中 certificate-authority 对应的 crt 或是 pem 文件,K8S 的最高权限证书
  • ID:可不填写,默认会自动生成一串字符串,也可以自行设置
  • 描述:描述下这个凭据的作用,比如这个可以写 对接 K8S 集群凭据

填写完毕,点击确定。

配置 K8S 集群的对接

登录 Jenkins,点击 系统管理 → 系统配置 → 滑动到页面最下面

点击 a separate configuration page:

  • Kubernetes 地址:kubernetes服务地址,也就是 apiserver 的地址,一般是master 节点 NodeIP+6443 端口
  • Kubernetes 服务证书 key:kube-ca.crt 文件的内容
  • 凭据:刚才创建的 certificate 凭据
  • Jenkins 地址:Agent 连接 Jenkins Master 的地址

其他都使用默认配置,点击连接测试,连接测试成功,点击 Save 存储。

K8S pod template 配置

Jenkins 的 kubernetes-plugin 在执行构建时会在 kubernetes 集群中自动创建一个 Pod,并在 Pod 内部创建一个名为 jnlp 的容器,该容器会连接 Jenkins 并运行 Agent 程序,形成一个 Jenkins 的 Master 和 Slave 架构,然后 Slave 会执行构建脚本进行构建,但如果构建内容是要创建 Docker Image 就要实现 Docker In Docker 方案(在 Docker 里运行 Docker),如果要在集群集群内部进行部署操作可以使用 kubectl 执行命令,要解决 kubectl 的安装和权限分配问题。

为了方便配置一个 Pod Templates,在配置 kubernetes 连接内容的下面,这里的模板只是模板(与类一样使用时还要实例化过程),名称和标签列表不要以为是 Pod 的 name 和 label,这里的名称和标签列表只是 Jenkins 查找选择模板时使用的,Jenkins 自动创建 Pod 的 name 是项目名称+随机字母的组合,所以我们填写 jenkins-slave,命名空间填写对应的 namespace。

这边要注意,添加 2 个 container,第一个,Pod 内添加一个容器名称是 jnlp,Docker 镜像填写:jenkins/jnlp-slave:4.3-7,后面的使用默认的即可,然后在添加一个 container,容器名称是 jnlp-kubectl,是这个容器里面有 kubectl 的命令,镜像名称填写 harbor.edu.cn/library/centos-docker-kubectl:v1.0,下面增加了 Host Path Volume:/var/run/docker.sock、/root/.kube/、/etc/kubernetes/pki,这边便是为了 jenkins-slave 下有足够的权限可以执行 docker 及 kubectl 部署到 k8s 集群的权限,因为 jenkins-slave pod 有可能会被调度到任一 worker 节点,所以所有的 worker 节点上都必须有 /root/.kube/、/etc/kubernetes/pki,配置好之后点击保存。

Jenkins pipeline 说明

Pipeline,简单来说,就是一套运行在 Jenkins 上的工作流框架,将原来独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂流程编排和可视化的工作。

Jenkins Pipeline 有几个核心概念:

  • Node:节点,一个 Node 就是一个 Jenkins 节点,Master 或者 Agent,是执行 Step 的具体运行环境,比如我们之前动态运行的 Jenkins Slave 就是一个 Node 节点
  • Stage:阶段,一个 Pipeline 可以划分为若干个 Stage,每个 Stage 代表一组操作,比如:Build、Test、Deploy,Stage 是一个逻辑分组的概念,可以跨多个 Node
  • Step:步骤,Step 是最基本的操作单元,可以是打印一句话,也可以是构建一个 Docker 镜像,由各类 Jenkins 插件提供,比如命令:sh 'make',就相当于我们平时 shell 终端中执行 make 命令一样。

Pipeline的使用:

  • Pipeline 脚本是由 Groovy 语言实现的
  • Pipeline 支持两种语法:Declarative(声明式)和 Scripted Pipeline(脚本式)语法
  • Pipeline 也有两种创建方法:可以直接在 Jenkins 的 Web UI 界面中输入脚本;也可以通过创建一个 Jenkinsfile 脚本文件放入项目源码库中
  • 一般我们都推荐在 Jenkins 中直接从源代码控制(SCMD)中直接载入 Jenkinsfile Pipeline 这种方法,但是本次为了更直观的展示,我们在 Web UI 界面中输入脚本

Jenkins pipeline 入门

创建并运行 pipeline
  • 进入到 Jenkins 首页,点击项目 prometheus-test-demo,点击左侧 配置
  • 点击页面顶部的 Pipeline 选项卡,向下滚动到 Pipeline 部分
  • 定义 域中,选择 Pipeline script 选项
node {
  stage('Clone') {
    echo "1.Clone Stage"
  }
  stage('Test') {
    echo "2.Test Stage"
  }
  stage('Build') {
    echo "3.Build Stage"
  }
  stage('Deploy') {
    echo "4. Deploy Stage"
  }
}

点击保存,切换到 Jenkins 页面,点击左侧的 打开 Blue Ocean 进入Jenkins的Blue Ocean界面,进入到 相应的项目下,点击 运行 。

也可以在 Jenkins prometheus-test-demo 项目下,点击左侧菜单 立即构建,然后点击正在构建的任务,就可以看到 Console Output:

在 Slave 中运行 Pipeline

上面对 Jenkins 的 Pipeline 做了简单的测试,但是其并未在我们的 Slave中运行,如果要在 Slave 中运行,其就要使用我们在对接 K8S 集群时 Pod Template 指定的标签列表 ,点击进 prometheus-test-demo 项目,点击左侧菜单 配置,进入到 pipeline scripts 部分,修改 pipeline scripts 如下:

node('slave') {
  stage('Clone') {
    echo "1.Clone Stage"
  }
  stage('Test') {
    echo "2.Test Stage"
  }
  stage('Build') {
    echo "3.Build Stage"
  }
  stage('Deploy') {
    echo "4. Deploy Stage"
  }
}

点击 立即构建,同时可以登录到k8s集群,使用 kubectl get po -w 可以看到 jenkins-slave pod 的生命周期,就是我们开始构建这个任务,选择了使用 jenkins slave,所以在执行过程中jenkins-slave就会自动创建,任务执行完成,jenkins-slave 对应的pod会自动回收:

在构建日志里我们也可以看到 jenkins 启动了 jenkins-slave-dj3vc pod 进行这个任务的执行,也和上面的 pod 名称对应起来了。

完整 pipeline 示例

部署应用的流程如下:

  • 拉取 Github 代码
  • maven 打包
  • 编写 Dockerfile
  • 构建打包 Docker 镜像
  • 推送 Docker 镜像到仓库
  • 编写 Kubernetes YAML 文件
  • 更改 YAML 文件中 Docker 镜像 TAG
  • 利用 kubectl 工具部署应用

最终的 Pipeline 脚本如下:

pipeline {
    agent none
    stages {
        stage('Clone Code') {
            agent {
                label 'master'
            }
            steps {
                echo "1.Git Clone Code"
                git url: "https://github.com/0820sdd/prometheus-test-demo.git"
            }
        }
        stage('Maven Build') {
            agent {
                docker {
                    image 'maven:latest'
                    args '-v /root/.m2:/root/.m2'
                }
            }
            steps {
                echo "2.Maven Build Stage"
                sh 'mvn -B clean package -Dmaven.test.skip=true'
            }
        }
        stage('Image Build') {
            agent {
                label 'master'
            }
            steps {
            echo "3.Image Build Stage"
            sh 'docker build -f Dockerfile --build-arg jar_name=target/prometheus-test-demo-0.0.1-SNAPSHOT.jar -t prometheus-test-demo:${BUILD_ID} . '
            sh 'docker tag  prometheus-test-demo:${BUILD_ID}  harbor.edu.cn/library/prometheus-test-demo:${BUILD_ID}'
            }
        }
        stage('Push') {
            agent {
                label 'master'
            }
            steps {
            echo "4.Push Docker Image Stage"
            sh "docker login --username=admin harbor.edu.cn -p Harbor12345"
            sh "docker push harbor.edu.cn/library/prometheus-test-demo:${BUILD_ID}"
            }
        }
    }
}
 
node('slave') {
    container('jnlp-kubectl') {
        
        stage('Clone YAML') {
        echo "5. Git Clone YAML To Slave"
        git url: "https://github.com/0820sdd/prometheus-test-demo.git"
        }
        
        stage('YAML') {
        echo "6. Change YAML File Stage"
        sh 'sed -i "s#{VERSION}#${BUILD_ID}#g" ./jenkins/scripts/prometheus-test-demo.yaml'
        }
    
        stage('Deploy') {
        echo "7. Deploy To K8s Stage"
        sh 'kubectl apply -f ./jenkins/scripts/prometheus-test-demo.yaml'
        }
    }
}

注意,prometheus-test-demo.yaml 可放在 GitHub 的 prometheus-test-demo 仓库里,也可以另外新建一个仓库专门放 YAML 文件。

下面我们分解讲下上面过程的具体含义:

克隆代码
        stage('Clone to master') {
            agent {
                label 'master'
            }
            steps {
                echo "1.Git Clone Stage"
                git url: "https://github.com/0820sdd/prometheus-test-demo.git"
            }
        }

这步就是从 GitHub 上拉取代码,注意这边的 GitHub 仓库仓库比如是 公开的,因为 private 的需要各种权限配置,Jenkins 必须有一个公网 IP 或者是公网域名,但因资源问题,这部分暂时没有办法实现。注意,这边 agent 里面指定运行环境,选择了 master,即是这个步骤在 Jenkins master节点执行。

maven 打包
        stage('Maven Build') {
            agent {
                docker {
                    image 'maven:latest'
                    args '-v /root/.m2:/root/.m2'
                }
            }
            steps {
                echo "2.Maven Build Stage"
                sh 'mvn -B clean package -Dmaven.test.skip=true'
            }
        }

maven 构建,我们指定了 maven 打包的 agent 是在 Jenkins 所在节点另起一个 docker 容器,容器的 image 为 maven:latest,并且使用 -v 参数把本地的 /root/.m2 挂载到 容器的 /root/.m2 目录下,下面 steps 的步骤即是在这个 maven 容器里面的具体操作:mvn -B clean package -Dmaven.test.skip=true。

构建镜像
        stage('Image Build') {
            agent {
                label 'master'
            }
            steps {
            echo "3.Image Build Stage"
            sh 'docker build -f Dockerfile --build-arg jar_name=target/prometheus-test-demo-0.0.1-SNAPSHOT.jar -t prometheus-test-demo:${BUILD_ID} . '
            sh 'docker tag  prometheus-test-demo:${BUILD_ID}  harbor.edu.cn/library/prometheus-test-demo:${BUILD_ID}'
            }
        }

maven 构建成功,下一步就是使用 maven build 生成的 prometheus-test-demo-0.0.1-SNAPSHOT.jar 包进行 docker build,docker build 的具体命令有2条 bash 命令 组成,第一步 docker build 使用 -f 指定了 Dockerfile 的文件,使用--build-arg 参数指定了一些参数,比如上面指定了 jar_name 是 target/prometheus-test-demo-0.0.1-SNAPSHOT.jar,最后使用 -t 参数指定了 docker build 的 image 的名称及版本号。第二步就是 使用 docker tag 命令把上一步 docker build 完成的镜像 打 tag 为 harbor.edu.cn/library/prometheus-test-demo:${BUILD_ID},这步打 tag 的步骤是为了上传到 harbor 镜像仓库,可以随时使用。

推送镜像
        stage('Push') {
            agent {
                label 'master'
            }
            steps {
            echo "4.Push Docker Image Stage"
            sh "docker login --username=admin harbor.edu.cn -p Harbor12345"
            sh "docker push harbor.edu.cn/library/prometheus-test-demo:${BUILD_ID}"
            }
        }

镜像 build 完成,就可以使用 docker push 命令推送到 harbor.edu.cn 镜像仓库。

拉取镜像
 node('slave') {
    container('jnlp-kubectl') {
        
        stage('Clone YAML') {
        echo "5. Git Clone YAML To Slave"
        git url: "https://github.com/0820sdd/prometheus-test-demo.git"
        }
     }
  }

现在镜像已经打包完成,并推送到了镜像仓库,后面我们所要做的就是拉取 k8s 编排文件,这一步和第一步的 拉取代码实际是一样的,只不过上面的拉取代码是为了 build image,这一步是为了进行部署到 K8S。
注意:这边指定了运行此步骤的节点是在 Jenkins 的 slave 节点下的 jnlp-kubectl container 下,这个 slave 是指在配置 对接 K8S 集群时,在 Pod Template 下指定的 标签列表的名称,必须与这个名称一致,不然 jenkins 执行过程中就会报找不到对应的 label 。还有这边指定了 jnlp-kubectl container ,这是因为 jnlp-kubectl container下有 kubectl 命令,且配置 对接 K8S 集群时,指定了把宿主机的 /root/.kube /etc/kubernetes/pki 目录分别挂载到 container 的 /root/.kube /etc/kubernetes/pki目录下,这边就是 jnlp-kubectl container 可以访问 K8S 集群的原因。

替换 YAML 文件变量
        stage('YAML') {
        echo "6. Change YAML File Stage"
        sh 'sed -i "s#{VERSION}#${BUILD_ID}#g" ./jenkins/scripts/prometheus-test-demo.yaml'
        }

yaml文件拉取完毕,替换其中的变量。

部署
        stage('Deploy') {
            echo "5. Deploy To K8s Stage"
            sh 'kubectl apply -f ./jenkins/scripts/prometheus-test-demo.yaml -n default'
        }

使用 kubectl 命令部署 prometheus-test-demo 应用到 K8S 集群。

最终执行效果如下:
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335