kubernetes-ci/cd-(二)

基于jenkins pipeline进行部署

1、jenkins pipeline介绍

    要实现在 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 命令一样。

那么我们如何创建 Jenkins Pipline 呢?

  • Pipeline 脚本是由 Groovy 语言实现的,但是我们没必要单独去学习 Groovy,当然你会的话最好
  • Pipeline 支持两种语法:Declarative(声明式)和 Scripted Pipeline(脚本式)语法
  • Pipeline 也有两种创建方法:可以直接在 Jenkins 的 Web UI 界面中输入脚本;也可以通过创建一个 Jenkinsfile 脚本文件放入项目源码库中
  • 一般我们都推荐在 Jenkins 中直接从源代码控制(SCMD)中直接载入 Jenkinsfile Pipeline 这种方法创建一个简单的 Pipeline

    我们这里来给大家快速创建一个简单的 Pipeline,直接在 Jenkins 的 Web UI 界面中输入脚本运行。

  • 新建 Job:在 Web UI 中点击 New Item -> 输入名称:pipeline-demo -> 选择下面的 Pipeline -> 点击 OK
  • 配置:在最下方的 Pipeline 区域输入如下 Script 脚本,然后点击保存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
shell 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"
}
}
  • 构建:点击左侧区域的 Build Now,可以看到 Job 开始构建了

    隔一会儿,构建完成,可以点击左侧区域的 Console Output,我们就可以看到如下输出信息:

img

  • 在 Slave 中构建任务
        上面我们创建了一个简单的 Pipeline 任务,但是我们可以看到这个任务并没有在 Jenkins 的 Slave 中运行,那么如何让我们的任务跑在 Slave 中呢?还记得上节课我们在添加 Slave Pod 的时候,一定要记住添加的 label 吗?没错,我们就需要用到这个 label,我们重新编辑上面创建的 Pipeline 脚本,给 node 添加一个 label 属性,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
node('jnlp-agent') {
stage('Clone') {
echo "1.Clone Stage"
}
stage('Test') {
echo "2.Test Stage"
}
stage('Build') {
echo "3.Build Stage"
}
stage('Deploy') {
echo "4. Deploy Stage"
}
}

这里只是给 node 添加了一个jnlp-agent这样的一个label,然后我们保存,构建之前查看下 kubernetes 集群中的 Pod:

1
2
3
4
5
6
7
8
[test] [root@k8s-zxc-test-3 ~]# kubectl get pods -n kube-ops
NAME READY STATUS RESTARTS AGE
jenkins2-696b8fbdbb-q24nm 1/1 Running 0 45h
jnlp-agent-342fv 0/1 ContainerCreating 0 0s
[test] [root@k8s-zxc-test-3 ~]# kubectl get pods -n kube-ops
NAME READY STATUS RESTARTS AGE
jenkins2-696b8fbdbb-q24nm 1/1 Running 0 45h
jnlp-agent-342fv 0/1 ContainerCreating 0 1s

img

  • kubernetes 界面显示
    img

  • jenkins执行结果显示
    img

     证明我们当前的任务在跑在上面动态生成的这个 Pod 中,也符合我们的预期。我们回到 Job 的主界面,也可以看到大家可能比较熟悉的 Stage View 界面:
img

部署 Kubernetes 应用

    我们已经知道了如何在 Jenkins Slave 中构建任务了,那么如何来部署一个原生的 Kubernetes 应用呢? 要部署 Kubernetes 应用,我们就得对我们之前部署应用的流程要非常熟悉才行,我们之前的流程是怎样的:

  • 1、编写代码
  • 2、测试
  • 3、编写 Dockerfile
  • 4、构建打包 Docker 镜像
  • 5、推送 Docker 镜像到仓库
  • 6、编写 Kubernetes YAML 文件
  • 7、更改 YAML 文件中 Docker 镜像 TAG
  • 8、利用 kubectl 工具部署应用

    我们之前在 Kubernetes 环境中部署一个原生应用的流程应该基本上是上面这些流程吧?现在我们就需要把上面这些流程放入 Jenkins 中来自动帮我们完成(当然编码除外),从测试到更新 YAML 文件属于 CI 流程,后面部署属于 CD 的流程。如果按照我们上面的示例,我们现在要来编写一个 Pipeline 的脚本。

  • 修改test-spring-social-wechat-sample pipeline脚本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    node('jnlp-agent') {
    stage('Clone') {
    echo "1.Clone Stage"
    }
    stage('Test') {
    echo "2.Test Stage"
    }
    stage('Build') {
    echo "3.Build Docker Image Stage"
    }
    stage('Push') {
    echo "4.Push Docker Image Stage"
    }
    stage('YAML') {
    echo "5. Change YAML File Stage"
    }
    stage('Deploy') {
    echo "6. Deploy Stage"
    }
    }
  • 1)、增加git地址,进行代码的clone

    1
    2
    3
    4
    stage('Clone') {
    echo "1.Clone Stage"
    git url: "https://github.com/xxlaila/jenkins-demo.git"
    }
  • 2)、进行测试

    1
    2
    3
    stage('Test') {
    echo "2.Test Stage"
    }
  • 3)、构建一个docker镜像

    1
    2
    3
    4
    stage('Build') {
    echo "3.Build Docker Image Stage"
    sh "docker build -t xxlaila/jenkins-demo:${build_tag} ."
    }

    平时构建的时候是不是都是直接使用docker build命令进行构建就行了,那么这个地方呢?我们上节课给大家提供的 Slave Pod 的镜像里面是不是采用的 Docker In Docker 的方式,也就是说我们也可以直接在 Slave 中使用 docker build 命令,所以我们这里直接使用 sh 直接执行 docker build 命令即可,但是镜像的 tag 呢?如果我们使用镜像 tag,则每次都是 latest 的 tag,这对于以后的排查或者回滚之类的工作会带来很大麻烦,我们这里采用和git commit的记录为镜像的 tag,这里有一个好处就是镜像的 tag 可以和 git 提交记录对应起来,也方便日后对应查看。但是由于这个 tag 不只是我们这一个 stage 需要使用,下一个推送镜像是不是也需要,所以这里我们把这个 tag 编写成一个公共的参数,把它放在 Clone 这个 stage 中,修改前面两个 stage:

1
2
3
4
5
6
7
8
9
10
11
stage('Clone') {
echo "1.Clone Stage"
git url: "https://github.com/xxlaila/jenkins-demo.git"
script {
build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
}
}
stage('Build') {
echo "3.Build Docker Image Stage"
sh "docker build -t xxlaila/jenkins-demo:${build_tag} ."
}
  • 4)、推送镜像
        镜像构建完成了,现在我们就需要将此处构建的镜像推送到镜像仓库中去,当然如果你有私有镜像仓库也可以,这里还没有自己搭建私有的仓库,所以直接使用 docker hub 即可。
        我们知道 docker hub 是公共的镜像仓库,任何人都可以获取上面的镜像,但是要往上推送镜像我们就需要用到一个帐号了,所以我们需要提前注册一个 docker hub 的帐号,记住用户名和密码,我们这里需要使用。正常来说我们在本地推送 docker 镜像的时候,是不是需要使用docker login命令,然后输入用户名和密码,认证通过后,就可以使用docker push命令来推送本地的镜像到 docker hub 上面去了,如果是这样的话,我们这里的 Pipeline 是不是就该这样写了:
1
2
3
4
5
stage('Push') {
echo "4.Push Docker Image Stage"
sh "docker login -u cq_xxlaila@163.com -p 111111"
sh "docker push xxlaila/jenkins-demo:${build_tag}"
}

    如果只是在 Jenkins 的 Web UI 界面中来完成这个任务的话,我们这里的 Pipeline 是可以这样写的,但是我们是不是推荐使用 Jenkinsfile 的形式放入源码中进行版本管理,这样的话我们直接把 docker 仓库的用户名和密码暴露给别人这样很显然是非常非常不安全的,更何况我们这里使用的是 github 的公共代码仓库,所有人都可以直接看到我们的源码,所以我们应该用一种方式来隐藏用户名和密码这种私密信息,幸运的是 Jenkins 为我们提供了解决方法。
    在首页点击 Credentials -> Stores scoped to Jenkins 下面的 Jenkins -> Global credentials (凭据) ->system(系统)->全局凭据 (unrestricted)-> 左侧的 Add Credentials( 添加凭据):添加一个 Username with password 类型的认证信息,如下:
img

    Add Credentials 输入 docker hub 的用户名和密码,ID 部分我们输入dockerHub,注意,这个值非常重要,在后面 Pipeline 的脚本中我们需要使用到这个 ID 值。
    有了上面的 docker hub 的用户名和密码的认证信息,现在修改 Pipeline 中的第四部,使用这里的用户名和密码:

1
2
3
4
5
6
stage('Push') {
echo "4.Push Docker Image Stage"
withCredentials([usernamePassword(credentialsId: 'dockerHub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {
sh "docker login -u ${dockerHubUser} -p ${dockerHubPassword}"
sh "docker push xxlaila/jenkins-demo:${build_tag}"
}

注意:
    我们这里在 stage 中使用了一个新的函数withCredentials,其中有一个credentialsId值就是我们刚刚创建的 ID 值,然后就可以在脚本中直接使用这里两个变量值来直接替换掉之前的登录 docker hub 的用户名和密码,这样操作就相对来说就很安全了,只是传递进去了两个变量而已,别人并不知道真正用户名和密码,只有我们自己的 Jenkins 平台上添加的才知道。
测试结果:

img

  • 5)、更改 YAML
        上面已经完成了镜像的打包、推送的工作,接下来我们是不是应该更新 Kubernetes 系统中应用的镜像版本了,当然为了方便维护,我们都是用 YAML 文件的形式来编写应用部署规则,比如我们这里的 YAML 文件:(k8s.yaml)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # cat k8s.yaml
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
    name: jenkins-demo
    namespace: default
    spec:
    template:
    metadata:
    labels:
    app: jenkins-demo
    spec:
    containers:
    - image: xxlaila/jenkins-demo:<BUILD_TAG>
    imagePullPolicy: IfNotPresent
    name: jenkins-demo
    env:
    - name: branch
    value: <BRANCH_NAME>

    使用一个 Deployment 资源对象来管理 Pod,该 Pod 使用的就是我们上面推送的镜像,唯一不同的地方是 Docker 镜像的 tag 不是我们平常见的具体的 tag,而是一个 的标识,实际上如果我们将这个标识替换成上面的 Docker 镜像的 tag,是不是就是最终我们本次构建需要使用到的镜像?怎么替换呢?其实也很简单,我们使用一个sed命令就可以实现了:

1
2
3
4
stage('YAML') {
echo "5. Change YAML File Stage"
sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"
}

sed 命令就是将 k8s.yaml 文件中的 标识给替换成变量 build_tag 的值。

  • 6)、部署
        Kubernetes 应用的 YAML 文件已经更改完成了,之前我们手动的环境下,是不是直接使用 kubectl apply 命令就可以直接更新应用。当然这里只是写入到了 Pipeline 里面,思路都是一样的:
1
2
3
4
stage('Deploy') {
echo "6. Deploy Stage"
sh "kubectl apply -f k8s.yaml"
}
  • 点击jenkins进行构建
    img

当然,这里部署失败,先别管,证明流程是对的,可以这么走

img

    以上的配置基本已经完成,但是我们的实际项目实践过程中,可能还需要一些人工干预的步骤,比如我们提交了一次代码,测试也通过了,镜像也打包上传了,但是这个版本并不一定就是要立刻上线到生产环境的。我们可能需要将该版本先发布到测试环境、QA 环境、或者预览环境之类的,总之直接就发布到线上环境去还是挺少见的,所以我们需要增加人工确认的环节,一般都是在 CD 的环节才需要人工干预,比如我们这里的最后两步,我们就可以在前面加上确认,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
stage('YAML') {
echo "5. Change YAML File Stage"
def userInput = input(
id: 'userInput',
message: 'Choose a deploy environment',
parameters: [
[
#class: 'ChoiceParameterDefinition',
choices: "Dev\nTest\nUat\nDemo\nPord",
name: 'Env'
]
]
)
echo "This is a deploy step to ${userInput.Env}"
sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"
}

    这里使用了 input 关键字,里面使用一个 Choice 的列表来让用户进行选择,然后在我们选择了部署环境后,我们当然也可以针对不同的环境再做一些操作,比如可以给不同环境的 YAML 文件部署到不同的 namespace 下面去,增加不同的标签等等操作:

1
2
3
4
5
6
7
8
9
10
11
stage('Deploy') {
echo "6. Deploy Stage"
if (userInput.Env == "Dev"){
// deploy dev stuff
} else if (userInput.Env == "Test"){
// deploy test stuff
} else {
// deploy prod stuff
}
sh "kubectl apply -f k8s.yaml"
}

由于这一步也属于部署的范畴,所以我们可以将最后两步都合并成一步,我们最终的 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
35
36
37
38
39
40
41
42
43
44
45
46
47
node('node-jnlp') {
stage('Clone') {
echo "1.Clone Stage"
git url: "https://github.com/xxlaila/jenkins-demo.git"
script {
build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
}
}
stage('Test') {
echo "2.Test Stage"
}
stage('Build') {
echo "3.Build Docker Image Stage"
sh "docker build -t xxlaila/jenkins-demo:${build_tag} ."
}
stage('Push') {
echo "4.Push Docker Image Stage"
withCredentials([usernamePassword(credentialsId: 'dockerHub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {
sh "docker login -u ${dockerHubUser} -p ${dockerHubPassword}"
sh "docker push cnych/jenkins-demo:${build_tag}"
}
}
stage('Deploy') {
echo "5. Deploy Stage"
def userInput = input(
id: 'userInput',
message: 'Choose a deploy environment',
parameters: [
[
$class: 'ChoiceParameterDefinition',
choices: "Dev\nQA\nProd",
name: 'Env'
]
]
)
echo "This is a deploy step to ${userInput}"
sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"
if (userInput == "Dev") {
// deploy dev stuff
} else if (userInput == "QA"){
// deploy qa stuff
} else {
// deploy prod stuff
}
sh "kubectl apply -f k8s.yaml"
}
}

错误: 在jenkins执行构建的时候提示:

img

没有权限进行部署,下面进行权限的分配。

  • 查看kube-ops 下面的角色

    1
    2
    3
    # kubectl get role -n kube-ops
    NAME AGE
    jenkins2 2d6h
  • 查看role定义的资源权限

    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
    # kubectl get role jenkins2 -n kube-ops -o yaml
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
    creationTimestamp: "2019-01-14T03:07:25Z"
    name: jenkins2
    namespace: kube-ops
    resourceVersion: "2389179"
    selfLink: /apis/rbac.authorization.k8s.io/v1/namespaces/kube-ops/roles/jenkins2
    uid: 84762132-17a9-11e9-8991-fa163e14c5bd
    rules:
    - apiGroups:
    - ""
    resources:
    - pods
    verbs:
    - create
    - delete
    - get
    - list
    - patch
    - update
    - watch
    - apiGroups:
    - ""
    resources:
    - pods/exec
    verbs:
    - create
    - delete
    - get
    - list
    - patch
    - update
    - watch
    - apiGroups:
    - ""
    resources:
    - pods/log
    verbs:
    - get
    - list
    - watch
    - apiGroups:
    - ""
    resources:
    - secrets
    verbs:
    - get
  • 创建jenkins2的权限

    1
    2
    [root@k8s-zxc-test-3 ~]# kubectl -n kube-system create sa jenkins2
    serviceaccount/jenkins2 created
  • 授权访问

    1
    2
    [root@k8s-zxc-test-3 ~]# kubectl create clusterrolebinding jenkins2 --clusterrole cluster-admin --serviceaccount=kube-ops:jenkins2
    clusterrolebinding.rbac.authorization.k8s.io/jenkins2 created
坚持原创技术分享,您的支持将鼓励我继续创作!
0%