根据前面用户的反馈,这里补充一个完整的动手实践的案例——搭建“基于 K8s 和 Docker 的 Jenkins 可伸缩持续集成系统”,让模块 3 所介绍的内容落地。
这部分内容比较多且非常具体,包括 4 大部分:
Kubernetes (K8s)集群的部署,包括 kube-proxy、kubelet、docker 和 flanneld services 等安装;
企业级容器注册管理平台 Harbor 的安装部署,包括 Docker、Docker Compose 等安装;
采用 Jenkins pipeline 实现自动构建并部署至 K8s,包括建立 spring boot 示例工程、创建 Dockerfile 和 Jenkinsfile、配置 jenkins pipeline 任务和 K8s 的 kube.config 到最后测试 pipeline 任务等;
遇到的问题(坑)及解决方法,比如启动 Jenkins,安装插件出现“无法连接服务器”错误,运行 pipeline,出现 command not found 错误等几个问题的解决。
这些具体的操作步骤经过了真实环境上的检验,最终整个基于 K8s 、Docker、Jenkins 的 CI 系统被成功部署起来。建议你按照下面介绍的详细步骤,自己亲自动手操作一回,功力会大增。
工作流程图
系统配置
Harbor 仓库 CentOS7、4 核 CPU、16G 内存、160G 硬盘
192.168.10.160 harbor
集群 3 台机器,CentOS7、4 核 CPU、16G 内存、60G 硬盘
192.168.10.161 k8s-master
192.168.10.162 k8s-node1
192.168.10.163 k8s-node2
Kubernetes 集群部署
安装前准备
(1)关闭 firewalld 改用 iptables。输入以下命令,关闭 firewalld:
1
2
|
\[root@master ~\]# systemctl stop firewalld.service #停止firewall \[root@master ~\]# systemctl disable firewalld.service #禁止firewall开机启动
|
(2)安装 ntp 服务:
1
2
|
\[root@master ~\]# yum install -y ntp wget net-tools \[root@master ~\]# systemctl start ntpd \[root@master ~\]# systemctl enable ntpd
|
安装配置
(1)安装 Kubernetes Master
使用以下命令安装 kubernetes 和 etcd:
1
2
|
# yum install -y kubernetes etcd
|
编辑 /etc/etcd/etcd.conf 使 etcd 监听所有的 IP 地址,确保下列行没有注释,并修改为下面的值:
1
2
|
\[root@master ~\]# cat /etc/etcd/etcd.conf ETCD\_LISTEN\_CLIENT\_URLS="http://0.0.0.0:2379" #\[cluster\] ETCD\_ADVERTISE\_CLIENT\_URLS="http://192.168.10.161:2379"
|
编辑 Kubernetes API server 的配置文件 /etc/kubernetes/apiserver,确保下列行没有被注释,并设置合适的值:
1
2
|
\[root@master ~\]# cat /etc/kubernetes/apiserver#### kubernetes system config## The following values are used to configure the kube-apiserver## The address on the local server to listen to.KUBE\_API\_ADDRESS="--address=0.0.0.0"# The port on the local server to listen on.KUBE\_API\_PORT="--port=8080"# Port minions listen onKUBELET\_PORT="--kubelet\_port=10250"# Comma separated list of nodes in the etcd clusterKUBE\_ETCD\_SERVERS="--etcd\_servers=http://192.168.10.161:2379"# Address range to use for servicesKUBE\_SERVICE\_ADDRESSES="--service-cluster-ip-range=10.254.0.0/16"# default admission control policiesKUBE\_ADMISSION\_CONTROL="--admission\_control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota"# Add your own!KUBE\_API\_A
|
启动 etcd、kube-apiserver、kube-controller-manager and kube-scheduler 服务,并设置开机自启:
1
2
|
\[root@master ~\]# cat /script/kubenetes\_service.shfor SERVICES in etcd kube-apiserver kube-controller-manager kube-scheduler; do systemctl restart $SERVICES systemctl enable $SERVICES systemctl status $SERVICES done\[root@master ~\]# sh /script/kubenetes\_service.sh
|
在 etcd 中定义 flannel network 的配置,这些配置会被 flannel service 下发到 nodes 中:
1
2
|
\[root@master ~\]# etcdctl mk /centos.com/network/config '{"Network":"172.17.0.0/16"}'
|
添加 iptables 规则,打开相应的端口:
1
2
|
\[root@master ~\]# iptables -I INPUT -p tcp --dport 2379 -j ACCEPT\[root@master ~\]# iptables -I INPUT -p tcp --dport 10250 -j ACCEPT\[root@master ~\]# iptables -I INPUT -p tcp --dport 8080 -j ACCEPT \[root@master ~\]# iptables-save
|
或者写入 iptables 配置文件 /etc/sysconfig/iptables。
查看节点信息(我们还没有配置节点信息,所以这里应该为空):
1
2
|
\[root@master ~\]# kubectl get nodesNAME LABELS STATUS
|
(2)安装 Kubernetes Nodes
注:下面这些步骤应该在 node1 和 node2 上执行(也可以添加更多的 node)。
使用 yum 安装 kubernetes 和 flannel:
1
2
|
\[root@slave1 ~\]# yum install -y flannel kubernetes
|
为 flannel service 配置 etcd 服务器,编辑 /etc/sysconfig/flanneld 文件中的下列行以连接到 master:
1
2
|
\[root@slave1 ~\]# cat /etc/sysconfig/flanneldFLANNEL\_ETCD="http://192.168.10.161:2379" #改为etcd服务器的ipFLANNEL\_ETCD\_PREFIX="/centos.com/network"
|
编辑 /etc/kubernetes/config 中 kubernetes 的默认配置,以确保 KUBE_MASTER 的值连接到 Kubernetes master API server:
1
2
|
\[root@slave1 ~\]# cat /etc/kubernetes/configKUBE\_MASTER="--master=http://192.168.10.161:8080"
|
编辑 /etc/kubernetes/kubelet 中五个参数的值:
node1:
1
2
|
\[root@slave1 ~\]# cat /etc/kubernetes/kubeletKUBELET\_ADDRESS="--address=0.0.0.0"KUBELET\_PORT="--port=10250"KUBELET\_HOSTNAME="--hostname\_override=192.168.10.162"KUBELET\_API\_SERVER="--api\_servers=http://192.168.10.161:8080"KUBELET\_ARGS=""
|
node2:
1
2
|
\[root@slave2 ~\]# cat /etc/kubernetes/kubeletKUBELET\_ADDRESS="--address=0.0.0.0"KUBELET\_PORT="--port=10250"KUBELET\_HOSTNAME="--hostname\_override=192.168.10.163"KUBELET\_API\_SERVER="--api\_servers=http://192.168.10.161:8080"KUBELET\_ARGS=""
|
启动 kube-proxy、kubelet、docker 和 flanneld services 服务,并设置开机自启:
1
2
|
\[root@slave1 ~\]# cat /script/kubernetes\_node\_service.shfor SERVICES in kube-proxy kubelet docker flanneld; do systemctl restart $SERVICESsystemctl enable $SERVICESsystemctl status $SERVICES done
|
在每个 node 节点上,你应当注意到有两块新的网卡 docker0 和 flannel0,应该得到不同的 IP 地址范围在 flannel0 上,就像下面这样:
node1:
1
2
|
\[root@slave1 ~\]# ip a | grep docker | grep inetinet 172.17.0.1/16 scope global docker0
|
node2:
1
2
|
\[root@slave2 ~\]# ip a | grep docker | grep inetinet 172.17.60.0/16 scope global docker0
|
添加 iptables 规则:
1
2
|
\[root@slave1 ~\]# iptables -I INPUT -p tcp --dport 2379 -j ACCEPT\[root@slave1 ~\]# iptables -I INPUT -p tcp --dport 10250 -j ACCEPT\[root@slave1 ~\]# iptables -I INPUT -p tcp --dport 8080 -j ACCEPT
|
现在登录 kubernetes master 节点验证 minions 的节点状态:
1
2
|
\[root@master ~\]# kubectl get nodesNAME STATUS AGE192.168.10.162 Ready 2h192.168.10.163 Ready 2h
|
至此,Kubernetes 集群已经配置并运行了,然后我们继续下面的步骤。
Harbor 安装部署
Harbor 是 VMWare 公司开源的企业级 Docker Registry 项目,项目地址是https://github.com/goharbor/harbor。
下载离线安装包
下载地址 https://github.com/goharbor/harbor/releases。
机器配置要求:
将下载的安装包上传到服务器,运行以下命令解压:
tar zxvf harbor-offline-installer-v1.10.1.tgz
安装 Docker
1
2
|
\# 安装依赖包yum install -y yum-utils device-mapper-persistent-data lvm2\# 添加Docker软件包源yum-config-manager \\ --add-repo \\ https://download.docker.com/linux/centos/docker-ce.repo\# 安装Docker CEyum install -y docker-ce\# 启动Docker服务并设置开机启动systemctl start dockersystemctl enable docker
|
安装 docker-compose
Docker Compose 是 Docker 提供的一个命令行工具,用来定义和运行由多个容器组成的应用。使用 compose,我们可以通过 YAML 文件声明式的定义应用程序的各个服务,并由单个命令完成应用的创建和启动。
执行以下命令进行安装:
1
2
|
yum install epel-release yum install -y python-pip pip install docker-compose yum install git
|
Harbor 安装与配置
修改 harbor.yml:
hostname 这里设置本机的 IP
harbor_admin_password web 页面的密码
运行:
sh ./install.sh
访问页面 http://192.168.10.160/:
Docker 主机访问 Harbor
在另外一个服务器(client)登录 Harbor:
1
2
|
\# docker login 192.168.10.160Username: adminPassword: Error response from daemon: Get https:// 192.168.10.160/v2/: dial tcp 192.168.10.160:443: connect: connection refused
|
这是因为 docker1.3.2 版本开始默认 docker registry 使用的是 https,我们设置 Harbor 默认为 http 方式,所以当执行用 docker login、pull、push 等命令操作而非 https 的 docker regsitry 的时就会报错。
解决 https
在 harbor 那台服务器上,其安装目录:
vi docker-compose.yml
修改 ports 信息
然后我们执行:
1
2
|
docker-compose stop./install.sh
|
然后同时编辑 harbor 和 client 的 docker 配置文件:
1
2
|
\# 1.vim /etc/docker/daemon.json { "insecure-registries": \["192.168.10.160"\]} \# 2.添加ExecStart=/usr/bin/dockerd |--insecure-registry=192.168.10.160vim /usr/lib/systemd/system/docker.service \# 把这行注释掉,添加下面的配置 ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sockExecStart=/usr/bin/dockerd |--insecure-registry=192.168.10.160
|
1. 重启 docker
1
2
|
systemctl daemon-reloadsystemctl restart docker
|
2.重启 harbor 的 docker-compose,命令如下:
docker-compose restart
client 登录仓库
1
2
|
\# docker login 192.168.10.160Username: adminPassword: Login Succeeded
|
采用 jenkins pipeline 实现自动构建并部署至 K8s
部署 jenkins
这里采用 yum install 的方式部署 jenkins。
(1)安装JDK:
yum install -y java
(2)安装 jenkins
添加 Jenkins 库到 yum 库,Jenkins 将从这里下载安装。
1
2
|
wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.reporpm --import https://jenkins-ci.org/redhat/jenkins-ci.org.keyyum install -y Jenkins
|
(3)配置 jenkis 的端口:
vi /etc/sysconfig/jenkins
找到修改端口号:
JENKINS\_PORT="8085" 此端口不冲突可以不修改
(4)启动 jenkins:
service jenkins start/stop/restart
(5)访问 http://localhost:8085 地址,等待出现下面解锁 jenkins 界面:
(6)在上图提示的密码文件中复制自动生成的密码。
(7)在解锁 jenkins 页面,粘贴密码并继续。
(8)解锁 jenkins 后,在界面中选择“安装建议的插件” 选项。
(9)最后,jenkins 要求创建管理员用户,创建新用户或使用 admin 用户,按照步骤完成后即可登录并使用 jenkis 了。
准备 java 示例工程
下面新建 spring boot 示例工程,示例工程的代码地址为:https://github.com/gemedia/docker-demo。
创建 spring boot 示例工程
(1)生成 spring boot 基础工程,添加一个示例 Controller 类。
1
2
|
package com.docker.demo.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class TestController { @GetMapping("") public String hello() { return "Hello!"; }
|
(2)修改 application 配置文件,设置端口。
1
2
|
spring.application.name=docker-demoserver.port=40080
|
(3)编译运行,访问 http://localhost:40080 地址可以看到示例运行结果。
添加 Dockerfile
在工程根目录创建 Dockerfile,用来构建 docker 镜像,其中 ${JAR_FILE} 参数在 pipeline 执行 docker build 时,通过 build-arg 参数传入。
1
2
|
FROM openjdk:8-jdk-alpine#构建参数ARG JAR\_FILEARG WORK\_PATH="/opt/demo"\# 环境变量ENV JAVA\_OPTS="" \\ JAR\_FILE=${JAR\_FILE}#设置时区RUN apk update && apk add ca-certificates && \\ apk add tzdata && \\ ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \\ echo "Asia/Shanghai" > /etc/timezoneCOPY target/$JAR\_FILE $WORK\_PATH/WORKDIR $WORK\_PATHENTRYPOINT exec java $JAVA\_OPTS -jar $JAR\_
|
添加 K8s 的 Deployment 配置
在工程根目录创建 k8s-deployment.tpl 文件,此文件用来作为 K8s 的 yaml 文件模板。在 jenkens pipeline 执行时,会先将 tpl 文件中 {} 括起来的自定义参数用 sed 命令替换为实际的内容。
1
2
|
apiVersion: apps/v1kind: Deploymentmetadata: name: {APP\_NAME}-deployment labels: app: {APP\_NAME}spec: replicas: 1 selector: matchLabels: app: {APP\_NAME} template: metadata: labels: app: {APP\_NAME} spec: containers: - name: {APP\_NAME} image: {IMAGE\_URL}:{IMAGE\_TAG} ports: - containerPort: 40080 env: - name: SPRING\_PROFILES\_ACTIVE value: {SPRING\_PROFILE}
|
添加 Jenkinsfile
在工程根目录创建 Jenkinsfile,用来执行 jenkins pipeline 任务。Jenkinsfile 文件的大概内容描述如下。
environment 中变量说明:
HARBOR_CREDS 为 harbor 镜像仓库的用户密码,数据保存为 jenkins 的“username and password”类型的凭据,用 credentials 方法从凭据中获取,使用时通过 HARBOR_CREDS_USR 获取用户名,HARBOR_CREDS_PSW 获取密码;
K8S_CONFIG 为 K8s 中 kubectl 命令的 yaml 配置文件内容,数据保存为 jenkins 的“Secret Text”类型的凭据,用 credentials 方法从凭据中获取,这里保存的 yaml 配置文件内容以 base64 编码格式保存,在设置凭据时先要进行 base64 编码(此 base64 编码是非必须的,如果直接保存原文,下面 Jenkinsfile 中需要去掉 base64 -d 解码) ;
GIT_TAG 变量通过执行 sh 命令获取当前 git 的 tag 值,由于后面构建 docker 镜像时使用 git 的 tag 作为镜像的标签,所以这个变量也不能为空。
parameters 中变量说明:
HARBOR_HOST,harbor 镜像仓库地址;
DOCKER_IMAGE,docker 镜像名,包含 harbor 项目名称;
APP_NAME,K8s 中的标签名称,对应 K8s 的 yaml 模板中的 {APP_NAME};
K8S_NAMESPACE,K8s 中的 namespace 名称,执行 kubectl 命令会部署至此命名空间。
stages 说明:
Maven Build,使用 docker 的方式执行 maven 命令,args 参数中将 .m2 目录映射出来,避免执行时重复从远端获取依赖;stash 步骤中将 jar 文件保存下来,供后面的 stage 使用;
Docker Build,unstash 获取 jar 文件,通过 sh 依次执行 docker 命令登录 harbor、构建镜像、上传镜像、移除本地镜像,构建镜像时,会获取 jar 文件名传入 JAR_FILE 参数;
Deploy,使用 docker 的方式执行 kubectl 命令,在执行前先将 K8S_CONFIG 中的内容进行 base64 解密并存为 ~/.kube/config 配置文件,然后执行 sed 命令将 k8s-deployment.tpl 文件中“{参数名}”形式参数替换为实际的参数值,最后执行 kubectl 命令部署至 K8s。
1
2
|
// 需要在jenkins的Credentials设置中配置jenkins-harbor-creds、jenkins-k8s-config参数pipeline { agent any environment { HARBOR\_CREDS = credentials('jenkins-harbor-creds') K8S\_CONFIG = credentials('jenkins-k8s-config') GIT\_TAG = sh(returnStdout: true,script: 'git describe --tags --always').trim() } parameters { string(name: 'HARBOR\_HOST', defaultValue: '192.168.10.160', description: 'harbor仓库地址') string(name: 'DOCKER\_IMAGE', defaultValue: 'tssp/pipeline-demo', description: 'docker镜像名') string(name: 'APP\_NAME', defaultValue: 'pipeline-demo', description: 'k8s中标签名') string(name: 'K8S\_NAMESPACE', defaultValue: 'demo', description: 'k8s的namespace名称') } stages { stage('Maven Build') { when { expression { env.GIT\_TAG != null } } agent { docker { image 'maven:3-jdk-8-alpine' args '-v $HOME/.m2:/root/.m2' } } steps { sh 'mvn clean package -Dfile.encoding=UTF-8 -DskipTests=true' stash includes: 'target/\*.jar', name: 'app' } } stage('Docker Build') { when { allOf { expression { env.GIT\_TAG != null } } } agent any steps { unstash 'app' sh "docker login -u ${HARBOR\_CREDS\_USR} -p ${HARBOR\_CREDS\_PSW} ${params.HARBOR\_HOST}" sh "docker build --build-arg JAR\_FILE=\`ls target/\*.jar |cut -d '/' -f2\` -t ${params.HARBOR\_HOST}/${params.DOCKER\_IMAGE}:${GIT\_TAG} ." sh "docker push ${params.HARBOR\_HOST}/${params.DOCKER\_IMAGE}:${GIT\_TAG}" sh "docker rmi ${params.HARBOR\_HOST}/${params.DOCKER\_IMAGE}:${GIT\_TAG}" } } stage('Deploy') { when { allOf { expression { env.GIT\_TAG != null } } } agent { docker { image 'lwolf/helm-kubectl-docker' } } steps { sh "mkdir -p ~/.kube" sh "echo ${K8S\_CONFIG} | base64 -d > ~/.kube/config" sh "sed -e 's#{IMAGE\_URL}#${params.HARBOR\_HOST}/${params.DOCKER\_IMAGE}#g;s#{IMAGE\_TAG}#${GIT\_TAG}#g;s#{APP\_NAME}#${params.APP\_NAME}#g;s#{SPRING\_PROFILE}#k8s-test#g' k8s-deployment.tpl > k8s-deployment.yml" sh "kubectl apply -f k8s-deployment.yml --namespace=${params.K8S\_NAMESPACE}" } } }}
|
配置 jenkins pipeline 任务
创建 jenkins pipeline 任务,并设置需要的参数。
新建 pipeline 任务
单击“新建任务”按钮,输入名称并选择“流水线”(pipeline),然后单击“确定”按钮。
配置 pipeline 任务
进入任务的配置界面,在流水线(pipeline)设置部分,选择“Pipeline script from SCM”选项。SCM 选项选为“Git”,配置好工程的 git 地址以及获取代码的凭证信息;然后在“Additional Behaviours”中添加“Clean before checkout”。
配置 harbor 账号与密码
选择“凭据”,然后在下图所示位置单击“添加凭据”按钮。在新凭据设置界面,类型选择为“Username with password”,ID 设置为“jenkins-harbor-creds”(此处的 ID 必须与 Jenkinsfile 中的保持一致)。Username 与 Password 分别设置为 harbor 镜像私库的用户名和密码。
配置 K8s 的 kube.config 信息
在 K8s 中使用 kubectl 命令时需要 yaml 格式的服务器以及授权信息配置文件,这里将 kubectl 的 yaml 配置文件的内容以 base64 编码后保存在 jenkins 的凭据中。pipeline 任务执行时,先从 jenkins 凭据中获取内容,进行 base64 解码后将配置保存为 ~/.kube/config 文件。kubectl 的配置文件的内容如下:
1
2
|
apiVersion: v1kind: Configclusters:\- name: "test" cluster: server: "https://xxxxx" api-version: v1 certificate-authority-data: "xxxxxx"users:\- name: "user1" user: token: "xxxx"contexts:\- name: "test" context: user: "user1" cluster: "test"current-context: "tes
|
可以在 Linux 中采用下面命令将 kubectl 的 yaml 配置文件进行 base64 编码。
1
2
|
base64 kube-config.yml > kube-config.txt
|
然后类似上一步,在 jenkins 凭据中增加配置文件内容。在凭据设置界面,类型选择为“Secret text”,ID 设置为“jenkins-k8s-config”(此处的 ID 必须与 Jenkinsfile 中的保持一致),Secret 设置为上面经过 base64 编码后的配置文件内容。
测试 pipeline 任务
在创建的 pipeline 任务中,单击“Build With Parameters”按钮,即可立即执行 pipeline 任务。
在当前界面中查看任务的执行结果,可以看到每个阶段的运行状态。
执行成功后,查看 harbor 镜像仓库,docker 镜像成功上传至 harbor。
在 Linux 服务器查看 deployment,运行以下命令:
1
2
|
kubectl get deployment
|
查看 pod:
遇到的问题及解决方法
(1)启动 Jenkins,安装插件出现“无法连接服务器”错误
安装插件那个页面,就是提示你 offline 的那个页面,不要动,然后打开一个新的 tab,输入网址 http://localhost:port/pluginManager/advanced。
这里面最底下有个“Update Site”,把其中的链接改成http://mirror.esuni.jp/jenkins/updates/update-center.json。
单击 submit 按钮:
然后重新启动 jenkins,这样就能正常安装插件。
(2)运行 pipeline,出现 command not found 错误
yum 安装的 Jenkins 配置文件默认位置 /etc/sysconfig/jenkins,默认 jenkins 服务以 jenkins 用户运行,这时在 jenkins 执行 ant 脚本时可能会发生没有权限删除目录、覆盖文件等情况。可以让 jenkins 以 root 用户运行来解决这个问题。
a.将 jenkins 账号分别加入到 root 组中:
1
2
|
gpasswd -a jenkins root
|
b.修改 /etc/sysconfig/jenkins 文件:
1
2
|
#user id to be invoked as (otherwise will run as root; not wise!)JENKINS\_USER=rootJENKINS\_GROUP=root
|
可以修改为 root 权限运行,重启 jenkins 服务。
(3)在 docker build 阶段出现 exec: “docker-proxy”: executable file not found in $PATH 错误,解决方法是,需要启动 docker-proxy:
1
2
|
cd /usr/libexec/docker/ ln -s docker-proxy-current docker-proxy
|
(4)出现 Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? 错误,运行如下命令:
1
2
|
systemctl daemon-reloadservice docker restart
|
(5)出现 shim error: docker-runc not installed on system. 错误,经过一番排查,如下解决方案有用:
1
2
3
4
|
cd /usr/libexec/docker/ ln -s docker-runc-current docker-runc
```\--- ### 精选评论
|