部署服务下

1991/6/26 基础

# 1.介绍

文章中涉及到的go-apppipeline完整代码、以及Containerd配置

链接: https://pan.baidu.com/s/1i-bjFDvWo7XwqPRK8dgf0w?pwd=yev5 提取码: yev5

# 2.Jenkins容器映射

# 2.1 映射软件说明

软件 说明
docker jenkins发布过程中,用于镜像制作。
goctl 生成制作镜像的Dockerfile和发布到k8s的配置文件,由go-zero提供
kubectl 将应用发布到K8s

# 2.2 映射相关代码

具体映射代码如下:

docker-compose.yaml

 ....
  jenkins:
    build:
    ...
    volumes:
      - ${VOLUMES_PATH}/jenkins/jenkins_home:/var/jenkins_home
      - ${JENKINS_DOCKER_SOCK}:/var/run/docker.sock # 映射宿主机的docker
      - ${JENKINS_DOCKER}:/usr/bin/docker # 映射宿主机的docker
      - ${KUBECTL_BIN_PATH}:/usr/local/bin/kubectl # 映射宿主机的kubectl
      - ${K8S_KUBE_CONFIG}:/root/.kube # 映射宿主机的kubectl
      - ${LIBLTDL_SO}:/usr/lib/x86_64-linux-gnu/libltdl.so.7
    ...
1
2
3
4
5
6
7
8
9
10
11
12

.env文件

...
KUBECTL_BIN_PATH=/usr/bin/kubectl
K8S_KUBE_CONFIG=/root/.kube
...
1
2
3
4

上述配置已经集成到k8s_microsvc_cicd: https://github.com/52lu/k8s_microsvc_cicd项目中,这里只做了解,无需操作;正常启动服务即可;

# 2.3 验证

进入jenkins验证上面软件是否映射到容器中.

# 进入容器
$ docker-compose exec jenkins bash
# 验证docker
$ docker version
Client: Docker Engine - Community
 Version:           20.10.22
 API version:       1.41
 Go version:        go1.18.9
 Git commit:        3a2c30b
 Built:             Thu Dec 15 22:30:24 2022
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true
 ...
# 验证goctl 
$ goctl -v
goctl version 1.4.3 linux/amd64
# 验证 kubectl
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"24", GitVersion:"v1.24.2"...
# 验证是否能连上k8s集群
$ kubectl get ns
NAME               STATUS   AGE
calico-apiserver   Active   17h
calico-system      Active   18h
default            Active   18h
ingress-nginx      Active   16h
kube-node-lease    Active   18h
kube-public        Active   18h
kube-system        Active   18h
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

# 2.4 踩坑:the server is currently unable...

如果在验证是否能连上k8s集群时,报错Error from server (ServiceUnavailable): the server is currently unable to handle the request,原因是发布机器上没有~/.kube/config这个文件。

解决方法:

  • master节点相同位置的配置文件,复制到发布机器~/.kube/config这个文件;
  • 重新编译jenkins: docker-compose build jenkins
  • 重新启动jenkins: docker-compose up -d jenkins

# 3. 配置准备

# 3.1 添加拉取代码凭证

添加步骤如下:

  • 点击左边菜单-> 系统管理(Manage Jenkins)
  • 点击 Manage Credentials
  • 点击“全局”后面的三角标,然后在点击“添加凭据”

进入添加凭据页面,填写信息如下:

  • 类型: 选择 Username with password 使用账号密码方式;
  • ID: 可以理解为证书的名称,需要唯一
  • 用户名: 登陆gitea的账号
  • 密码: 登陆gitea的密码

gitea设置使用ssh的方式拉取代码过于麻烦,这里使用账号密码的方式拉取,如果想使用ssh方式拉取代码,可参见官方文档设置: SSH 容器直通:https://docs.gitea.io/zh-cn/install-with-docker/#ssh-容器直通

填写完成后,点击保存(Create)

# 3.2 添加harbor仓库配置

进入首页,点击左侧菜单->系统管理(Manage Jenkins)->系统配置(Configure System) ,进入页面后一直往下滑动,直到到全局属性条目,添加docker私有仓库相关信息,如下图所示:

填完之后,点击 保存

上述信息结合自己的真实情况填写。

# 3.3 配置git

点击左侧菜单->系统管理(Manage Jenkins)-全局工具配置(Global Tool Configureation),找到Git条目,填写jenkins所在机器git可执行文件所在path,如果没有的话,需要在jenkins插件管理中下载Git插件, 有就不需要其他操作(如下图)

# 3.4 安装Git Parameter插件

jenkins发布过程中,编写管道(pipline)需要Git Parameter插件

点击左侧菜单->系统管理(Manage Jenkins)-插件管理(Manage Plugins)

# 3.5 k8s添加ServiceAccount

在后面 发布到k8s环节 ,会使用goctl kube xxx来生k8s yaml,在使用k8s方式部署时,需要为生成的k8s yaml中指定serviceAccount。 原理可以看这篇文章下方go-zerok8s服务发现讲解 :https://mp.weixin.qq.com/s/-WaWJaM_ePEQOf7ExNJe7w

只需要执行kubectl apply -f jenkins_service_account.yaml即可;

# 创建k8s命名空间
$ kubectl create ns k8s-demo
namespace/k8s-demo created
# 创建serviceAccount
$ kubectl apply -f jenkins_service_account.yaml
clusterrole.rbac.authorization.k8s.io/discov-endpoints created
clusterrolebinding.rbac.authorization.k8s.io/find-endpoints-discov-endpoints created
1
2
3
4
5
6
7

jenkins_service_account.yaml文件内容如下:

#创建账号
apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: k8s-demo
  name: find-endpoints

---
#创建角色对应操作
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: discov-endpoints
rules:
- apiGroups: [""]
  resources: ["endpoints"]
  verbs: ["get","list","watch"]

---
#给账号绑定角色
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: find-endpoints-discov-endpoints
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: discov-endpoints
subjects:
- kind: ServiceAccount
  name: find-endpoints
  namespace: k8s-demo
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

# 3.6 k8s添加拉取镜像凭证

k8s在默认情况下,只能拉取harbor镜像仓库的公有镜像,如果拉取私有仓库镜像,则是会报 ErrImagePullImagePullBackOff 的错误

# 1.先在jenkins发布机器登陆harbor

在jenkins发布机器登陆harbor,会生成登录凭证,保存在/root/.docker/config.json

$ docker login 192.168.148.132:8870
Username: admin
Password:
Error response from daemon: Get "https://192.168.148.132:8870/v2/": http: server gave HTTP response to HTTPS client
1
2
3
4

如果发现你也报这种错误的话,解决方法如下:

# 确保你的/etc/docker/daemon.json,加对了insecure-registries
$cat /etc/docker/daemon.json
{
    "registry-mirrors":[
        "https://otrqd6z7.mirror.aliyuncs.com"
    ],
    "insecure-registries":[
        "192.168.148.132:8870"
    ]
}
# 重新加载
$ systemctl daemon-reload
# 重启docker 
$ systemctl restart docker
# 重启harbor,在harbor安装目录执行下面命令
$ docker-compose restart
Restarting harbor-jobservice ... done
Restarting nginx             ... done
Restarting harbor-core       ... done
Restarting registry          ... done
Restarting redis             ... done
Restarting registryctl       ... done
Restarting harbor-portal     ... done
Restarting harbor-db         ... done
Restarting harbor-log        ... done
# 再次登录
$ docker login 192.168.148.132:8870
Username: admin
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
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

# 2.查看登陆harbor生成的凭证

$ cat .docker/config.json
{
    "auths":{
        "192.168.148.132:8870":{
            "auth":"YWRtaW46SGFyYm9yMTIzNDU="
        }
    }
}
1
2
3
4
5
6
7
8

# 3. 对秘钥文件进行base64加密

$ cat /root/.docker/config.json  | base64 -w 0

ewoJImF1dGhzIjogewoJCSIxOTIuMTY4LjE0OC4xMzI6ODg3MCI6IHsKCQkJImF1dGgiOiAiWVdSdGFXNDZTR0Z5WW05eU1USXpORFU9IgoJCX0KCX0KfQ==
1
2
3

# 4. 创建docker-harbor-secret.yaml

jenkins-harbor-secret.yaml内容如下:

apiVersion: v1
kind: Secret
metadata:
  name: docker-login
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: ewoJImF1dGhzIjogewoJCSIxOTIuMTY4LjE0OC4xMzI6ODg3MCI6IHsKCQkJImF1dGgiOiAiWVdSdGFXNDZTR0Z5WW05eU1USXpORFU9IgoJCX0KCX0KfQ==
# 在k8s集群中执行,注意指明命名空间
$ kubectl apply -f jenkins-harbor-secret.yaml -n k8s-demo
secret/docker-login created
1
2
3
4
5
6
7
8
9
10

# 4. Jenkins发布

# 4.1 创建任务

步骤一: 点击左侧菜单->新建任务(新建item),进入下面页面:

步骤二: 点击保存后跳到具体配置页面

# 4.2 Pipeline脚本预览

这一章节主要介绍Pipeline脚本都有哪些部分组成,并把pipeline具体步骤部分,单拿出一个章节讲解;

# 4.2.1 整体流程

pipeline {
  agent any
  parameters {
     // 接受页面输入的参数
  }
  environment {
    // 环境变量
  }
 // 具体构建步骤
  stages {
      stage('构建信息')    {
          steps {
              // 打印一些构建信息
          }
      }
      stage('拉取源码') {
          steps {
              // 拉取代码
          }
      }
      stage('生成镜像') {
          steps{
               //...
          }
      }

      stage('上传镜像') {
          steps{
             //...
          }
      }

      stage('发布到k8s') {
          steps{
            //...
          }
      }
       stage('清理代码') {
           steps{
            // 用来清理构建过程生成的信息
           }
       }
    }
}
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

# 4.2.2 参数信息

pipeline {
  agent any
  // 参数信息
  parameters {
      gitParameter (
        name: 'commit_id', // 参数名(我们在创建任务页面设置的git参数)
        type: 'PT_REVISION', // PT_TAG:标签,PT_BRANCH:分支
        branchFilter: 'origin/(.*)', // 不设置无法正常获取分支或标签
        defaultValue: 'master',
        selectedValue: 'TOP', // 定义选择的排序方式:NONE,TOP ,DEFAULT
        quickFilterEnabled: true, // 是否开启搜索功能
        sortMode: 'ASCENDING_SMART',
        description: '选择需要构建的commit信息'
     )
  }
  environment {
     //  环境变量
  }
  stages {
     //具体构建步骤
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 4.2.3 环境变量

pipeline {
  agent any
  parameters {
     // 参数信息 
  }
  // 环境变量
  environment {
     AppName = "go-app" // 项目名称
     AppPort = "1010" // 项目端口号,这里写死。正常应该走配置
     DockerNamespace = "k8s-demo" // harbor命名空间
     JenkinsAccountName= "find-endpoints" //k8s添加ServiceAccount的名称
     DeployReplicas = 1 // Deploy副本数
     PodMinReplicas = 1 // pod伸缩 最小副本数
     PodMaxReplicas = 1 // pod伸缩 最大副本数
  }
  stages {
     //具体构建步骤
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 4.3 Pipeline具体步骤

# 4.3.1 打印构建信息

stage('构建信息') {
  steps {
     // 也可以打印一些其他的信息
      sh 'echo commit_id:$commit_id'
      sh 'echo goctl版本信息'
      sh '/usr/local/bin/goctl -v'
  }
}
1
2
3
4
5
6
7
8

# 4.3.2 拉取源代码

这个步骤可以通过pipeline流水线语法生成,如下图:

stage('拉取源码') {
    steps {
       checkout([
         $class: 'GitSCM', // 不用改
         branches: [[name: '$commit_id']],// 和之前定义的变量名保持一直
         doGenerateSubmoduleConfigurations: false, // 不用改
         extensions: [],
         submoduleCfg: [],
         userRemoteConfigs: [[credentialsId: 'gitea-account', url: 'http://192.168.148.132:8800/k8s/go-app.git']] // gitea-account: 拉取凭证id;url:代码地址
       ])
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 4.3.3 生成镜像

 stage('生成镜像') {
   steps{
      sh "yes | rm -rf Dockerfile" // 删除历史Dockerfile
      sh "/usr/local/bin/goctl docker -go main.go && ls -l" // 生成dockerfile
      script{
          //保存镜像名称到环境变量
          env.image = sh(returnStdout: true, script: "echo ${env.AppName}:${commit_id}").trim()
      }
      sh "echo 镜像名称:${image}"
      // 构建镜像
      sh "docker build  -t ${image} ."
   }
 }
1
2
3
4
5
6
7
8
9
10
11
12
13

这里我们使用goctl来生成Dockerfile,

# 4.3.4 上传镜像

stage('上传镜像') {
    steps{
        //docker_username、docker_pwd、docker_repo是我们之前添加harbor仓库配置,DockerNamespace为harbor的命名空间
        sh 'docker login --username=${docker_username} --password=${docker_pwd} http://${docker_repo}'
        sh "docker tag  ${image} ${docker_repo}/${env.DockerNamespace}/${image}"
        sh "docker push ${docker_repo}/${env.DockerNamespace}/${image}"
    }
}
1
2
3
4
5
6
7
8

# 4.3.5 发布到k8s

stage('发布到k8s') {
    steps{
        script{
            env.deployYaml = sh(returnStdout: true, script: "echo ${env.AppName}-deploy.yaml").trim()
        }
        sh "echo 暴露端口: ${env.AppPort}"
        // 删除历史yaml
        sh 'rm -f ${deployYaml}'
        // 生成新yaml
        sh "/usr/local/bin/goctl kube deploy -secret docker-login -replicas ${env.DeployReplicas} -minReplicas ${env.PodMinReplicas}  -maxReplicas ${env.PodMaxReplicas} -nodePort 3${env.AppPort} -requestCpu 200 -requestMem 50 -limitCpu 300 -limitMem 100 -name ${env.AppName} -namespace ${env.DockerNamespace} -image ${docker_repo}/${env.DockerNamespace}/${image} -o ${deployYaml} -port ${env.AppPort} -serviceAccount ${env.JenkinsAccountName}"
        // 查看
        sh 'cat ${deployYaml}'
        // 发布
        sh '/usr/local/bin/kubectl apply -f ${deployYaml}'
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 4.3.6 清理代码

这一步主要是为了清理,构建过程中生成的代码信息,和镜像信息

stage('清理信息') {
    steps{
       // 忽略sh 错误
       sh 'set +e'
       sh "docker rmi  ${docker_repo}/${env.DockerNamespace}/${image}"
       sh "docker images | grep ${env.AppName} | awk \'{print \$3}\'| xargs docker rmi -f"
       sh "docker images | grep none | awk \'{print \$3}\'| xargs docker rmi -f "
       // 清理工作目录
       sh "ls -al ${env.WORKSPACE}"
       deleteDir() // 清理当前目录
       sh "ls -al ${env.WORKSPACE}"
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 4.4 准备发布

# 4.4.1 粘贴pipeline脚本

把上述的配置完整信息,复制粘贴到流水线脚本框,如下图:

# 4.4.2 发布

再次构建时,即可发现能发布成功,如下图:

# 4.4.3 查看k8s集群

服务启动失败原因:k8s集群的节点上,我们没有配置从私有仓库拉取镜像的配置;k8s各个集群节点安装的是Containerd,所以我们修改所有k8s集群节点的Containerd配置信息,并重启。

# 1. 修改Containerd配置
$  vim /etc/containerd/config.toml
...
# 找到这个位置
[plugins."io.containerd.grpc.v1.cri".registry]
  config_path = ""
  # 新增私有仓库
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors."192.168.148.132:8870"]
    endpoint = ["http://192.168.148.132:8870"]
  [plugins."io.containerd.grpc.v1.cri".registry.auths]
  [plugins."io.containerd.grpc.v1.cri".registry.configs]
    [plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.148.132:8870".tls]
      insecure_skip_verify = true #跳过证书验证
    [plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.148.132:8870".auth]
      username = "admin" # 账号
      password = "Harbor12345"# 密码
  [plugins."io.containerd.grpc.v1.cri".registry.headers]
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
 ....
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2. 重启Containerd

# 4.4.4 访问服务