本文将介绍如何通过在 Kubernetes 集群中利用 Jenkins 和 Kubernetes-Jenkins-Plugin 实现动态按需扩展 jenkins-slave 实现 CI/CD。实验环境为阿里云 ACK Kubernetes 托管集群。

0x00 前提条件:

已创建好的 Kubenretes 集群,包括自建集群和云服务提供商托管集群

在已创建的 Kubenretes 集群中安装好的 Jenkins 服务,有关安装方式:

  1. 使用云服务提供商控制台安装:阿里云

  2. 使用 Helm Charts 进行安装,参见:Configuring CI/CD on Kubernetes with Jenkins - Medium, Jenkins Chart - Helm

  3. 使用 Docker Hub 镜像自行安装,参见:基于 jenkins 打造 kubernetes on aws 上的 CI/CD 管道 - AWS, jenkins/jenkins - Docker Hub

0x01 安装 Kubernetes 插件:

以下进行的步骤依赖于 Jenkins 中 Kubernetes Plugin,需要在 Jenkins 设置内 Manage Plugins 页面自行安装。详情请参考 Jenkins Kubernetes Plugin

0x02 配置 Kubernetes 集群信息:

在全局设置中配置好 Kubernetes 集群的信息,进行连通性测试,由于本示例中针对 Jenkins 创建 Service Account 并赋予了权限,否则可能需要对 Credentials 进行配置。有关证书的配置生成:

  1. 在Jenkins的左侧导航栏中,选择系统管理。
  2. 在右侧的 Manage Jenkins 页面,单击系统设置。
  3. 在 Cloud 区域中,单击 Credentials 右侧的 Add。
  4. 将相应集群的 Kube Config 的信息填入对应的输入框中。

cluster-configutation

0x03 配置 Pod Template:

这一步配置 Dynamic Slaves 的 pod template, 构建过程中 master 节点就会按照这个 pod template 通过请求 Kubernetes 集群的 API Server 拉起一个容器并挂载为 Jenkins Slave 节点用于构建,而 pod template 具体应该包含哪些 container 很大程度上取决于你的应用构建方式和部署方式,下面的示例主要围绕 Docker 镜像打包构建流程和部署到 Kubernetes 集群的部署方式展开。

pod-template-base

查询各类资料的过程中,发现很多教程都会先自定义一个 jnlp-slave 的 container 用于和 master 主节点进行通信,而在本人实测的过程中发现,构建过程中会自动创建一个使用 jenkins/jnlp-slave:3.35-5-alpine 作为镜像的 jnlp-slave 的容器,即 pod template 只定义所需容器即可。需要注意的是大部分 alpine 版本的镜像会缺少部分常用的工具,例如实测过程中发现该镜像缺少 curl,导致钉钉推送出现问题,最后使用 wget 暂时解决。

在 Docker 镜像的构建方面,由于 Kubernetes 集群中缺少 Docker Daemon, 且考虑到 Docker 占用的空间较大,并没有采用打包带有 docker-ce 镜像的方式,而是使用了来自 Google 的 Kaniko Project,不依赖 Docker Daemon,可以直接运行在 Kubernetes 集群中。相关配置如下图:

kaniko-configuration

值得注意的是,本示例中使用的镜像为 gcr.io/kaniko-project/executor:debug,主要是因为在 tag latest 的镜像中缺少对 shell 脚本的支持,entrypoint 直接为 /kaniko/executor,故会对 pipeline 中执行一些脚本产生阻碍。至于国内的用户有个小技巧,可以使用 gcr.azk8s.cn/kaniko-project/executor:debug 避免被墙。

假设 Kaniko 编译镜像后需要推送到对应的镜像仓库,还需要配置对应仓库的权限,可以使用 kubernetes 的 secret,对应的配置如下:

registry-permissions

配置好 Kaniko 的容器模板后,接下来就需要配置部署容器了,实例中使用了自己打包的一个 docker 镜像最为部署容器的镜像,以下是该镜像的 dockerfile:

# 该镜像还可以用于在宿主机上配置 Docker in Docker 版本的 Jenkins
FROM jenkins/jenkins:centos

USER root

# https://get.helm.sh/helm-v3.1.2-linux-amd64.tar.gz

ENV ALICLI_VERSION aliyun-cli-linux-latest-amd64
ENV ALICLI_URL https://aliyuncli.alicdn.com/${ALICLI_VERSION}.tgz
ENV HELM_VERSION helm-v3.1.2
ENV HELM_PLATFORM linux-amd64
ENV HELM_URL https://get.helm.sh/${HELM_VERSION}-${HELM_PLATFORM}.tar.gz
ENV KUBECTL_VERSION v1.16.0
ENV KUBECTL_URL https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl

WORKDIR /tmp

# 安装 Kubectl
RUN curl -LO ${KUBECTL_URL} && \
    chmod +x ./kubectl && \
    mv ./kubectl /usr/bin/kubectl

# 安装 Helm
RUN curl -O ${HELM_URL} && \
    tar -xzvf ${HELM_VERSION}-${HELM_PLATFORM}.tar.gz && \
    chmod +x ./${HELM_PLATFORM}/helm && \
    mv ./${HELM_PLATFORM}/helm /usr/bin/helm && \
    rm -rf ${HELM_VERSION}-${HELM_PLATFORM}.tar.gz && rm -rf ./${HELM_PLATFORM}

# 安装 Aliyun-Cli
RUN curl -O ${ALICLI_URL} && \
    tar -xzvf ${ALICLI_VERSION}.tgz && \
    chmod +x ./aliyun && mv ./aliyun /usr/bin/aliyun && \
    rm -rf ${ALICLI_VERSION}.tgz

COPY ./config /home/.kube/config
ENV KUBECONFIG /home/.kube/config

USER jenkins

Jenkins 中部署容器配置如下:

kubectl-configuration

配置好相应容器的模板后,还有较多的自定义参数可供配置,例如容器的资源限制,构建 pod 留存策略和留存时间等,可以按需进行配置:

other-configuration

0x04 流水线配置

流水线的基本配置可以参考相关资料,此处给出供参考的 jenkinsfile:

#!/bin/env groovy

pipeline {
    agent {
        node {
            label 'devops-jenkins-build-agent' //
        }
    }

    stages {
        stage('初始化') {
            steps {
                echo '初始化'
                sh "git branch: 'test', credentialsId: '', url: 'https://github.com/xxx/jenkins-demo.git'"
            }
        }
        stage('构建') {
            environment {
                VERSION=sh(returnStdout: true, script:"sh scripts/get-version.sh").trim()
                IMAGE_REPOSITORY=sh(returnStdout: true, script:"sh scripts/get-backend-image-url.sh").trim()
            }
            steps {
                container(name: "kaniko", shell: '/busybox/sh') {
                    sh "/kaniko/executor -f ./Dockerfile -c `pwd` --destination=${IMAGE_REPOSITORY}:${VERSION} --skip-tls-verify"
                }
            }
        }
        stage('部署') {
            steps {
                container('kubectl') {
                    sh "kubectl apply -f ./test.yaml"
                }
            }
        }
    }
    post {
        always {
            echo '构建结束!!!!!'
        }
        success {
            echo '恭喜您,构建成功!!!'
            sh '''
            AUTHOR="`git log -1 --pretty=format:'%an'`"
            PROJECT="test"
            ENV="`sh scripts/get-env.sh`"
            MODULE="backend"
            VERSION="`bash scripts/get-version.sh`"
            RAW_BODY="`git log -1 --pretty=format:'%B'`"
            STATUS="恭喜您,发布成功!!!"
            sh scripts/notice-dinding.sh "$AUTHOR" "$PROJECT" "$ENV" "$MODULE" "$VERSION" "$RAW_BODY" "$STATUS"
            '''
        }
        failure {
            sh '''
            AUTHOR="`git log -1 --pretty=format:'%an'`"
            PROJECT="test"
            ENV="`sh scripts/get-env.sh`"
            MODULE="backend"
            VERSION="`bash scripts/get-version.sh`"
            RAW_BODY="`git log -1 --pretty=format:'%B'`"
            STATUS="抱歉,发布失败!!!"
            sh scripts/notice-dinding.sh "$AUTHOR" "$PROJECT" "$ENV" "$MODULE" "$VERSION" "$RAW_BODY" "$STATUS"
            '''
        }
        unstable {
            echo '该任务已经被标记为不稳定任务!!!'
        }
        cleanup {
            echo '清理环境!!!'
            sh "rm -rf ./*"
        }
    }
}

0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注