持续集成和部署如何做?一步步教你在k8s上安装Jenkins

持续集成和部署是 DevOps 的重要组成部分,Jenkins 是一款非常流行的持续集成和部署工具,最近试验了一下 Jenkins,发现它是我一段时间以来用过的工具中最复杂的。一个可能的原因是它需要与各种其它工具集成才能完成任务,而集成的方法又各不相同。在这些工具中,Docker 是最简单的,真的非常好用。K8s 比较复杂,开始要花些时间熟悉,但它的整体设计十分合理,一旦搞清核心概念,掌握脉络之后,就非常顺利。它的命令格式即规范又统一,使得有些命令自己都能猜出来,这就是好的设计带来的福利。。但 Jenkins 给人的感觉就是开始的时候没有设计得很好,后面在不断地打补丁,导致一件事情有好几种不同的做法,对不熟悉的人来讲无所适从。没有统一的风格,处处都是意外,使得整个系统看起来既庞杂又没有章法,当然这也跟它出来的时间比较长有关。虽然它可能不是最好的,但它是免费的,因此不能要求太高。

由于种种原因,我的 Jenkins 安装碰到了各种各样的问题,为此我查看了大量的资料。但遗憾的是每个人安装 Jenkins 的方法都有些不同,很难找到一篇文章能解决所有问题。在我看来,Jenkins 的安装有两三个关键之处,非常容易出错,一定要理解透彻才能成功。

本文分成两部分,第一部分讲正常安装步骤,如果一切顺利,就不需要看第二部分了。我只能说恭喜你,你的运气太好了。第二部分是讲各种问题及解决办法,这应该是本文最有价值的部分。

第一部分:在 k8s 上部署 Jenkins

1. 安装在什么地方?

容器化是大势所趋,它不但包括应用程序的容器化,还包括与之相关的工具的容器化。当把 Jenkins 部署在 K8s 上时,Jenkins 的主节点会根据情况自动生成子节点(新的容器)来完成任务,任务结束后会自动销毁子节点。

我先在 Windows 上部署了 VirtulBox 虚机,并用 Vagrant 来管理虚机,再在虚机上部署了 k8s。并通过 Vagrant 设置虚机和宿主机之间的网络共享,这样就可以在宿主机上用游览器直接访问 k8s 上的 Jenkins。另外还要把宿主机的硬盘挂载到 Jenkins 上,这样 Jenkins 的物理存储还是在宿主机上,即使虚机出了问题,所有的配置和数据都不会丢失。

2. 选择镜像文件

这个看起来不是问题,但是一不留神就容易出错。我就是因为选错了镜像,导致安装了很多遍,最后才成功,在本文的第二部分会详细说明。我最终用的镜像文件是“jenkinsci/jenkins:2.154-slim”,后来发现这个是比较旧的版本,新的镜像 是“jenkins/jenkins:lts”, 但因为已经安装成功了,就没有再换。Jenkins 真的很坑人,有三个镜像“Jenkins”,“jenkinsci/jenkins”, "jenkins/jenkins", 其中正确的是"jenkins/jenkins"。

选好镜像之后,可以先运行下面命令,下载 Jenkins 镜像文件到本地(虚机上)。vagrant@ubuntu-xenial:~$ docker pull jenkinsci/jenkins:2.154-slim

3. 安装 Jenkins 镜像:

在安装之前,需要先把宿主机的 Jenkins 安装目录挂载到虚机上,这样可以在本地直接操作 Jenkins。下面是 Vagrant 的配置文件(Vagrantfile)中的设置,它把宿主机的 app 目录挂载到虚机的"/home/vagrant/app"。Jenkins 就安装在 app 目录下。

config.vm.synced_folder "app/", "/home/vagrant/app", id: "app"

下面就是在宿主机上安装好了的 Jenkins 目录

目录

安装 Jenkins 镜像分成四部分,创建服务账户,安装持久卷,安装部署和安装服务,需要按顺序进行。其中的关键是创建服务账户,这个是必须的,没有它不会成功。不知为什么网上的有些文章没有提到它。

服务账户配置文件(service-account.yaml):

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: default
  name: service-reader
rules:
  - apiGroups: [""] # "" indicates the core API group
    resources: ["services"]
    verbs: ["get", "watch", "list"]
  - 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"]

这里创建了一个名为“service-reader”的“ClusterRole”,并把特定的权限(例如["get", "watch", "list"])赋给特定的资源(例如["services"])。

运行如下命令,创建一个名为“service-reader-pod”的集群角色绑定,它的“clusterrole”是“service-reader”,它的名字是“default:default”,其中第一个“default”是名空间(namespace),第二个“default”是服务账户名字,后面的部署配置文件会引用这个名字(default)。这里由于我没有给 Jenkins 创建单独的名空间,因此它用的默认名空间(“default”)。

kubectl create clusterrolebinding service-reader-pod --clusterrole=service-reader  --serviceaccount=default:default

关于服务账户的权限定义,请参阅“Kubernetes plugin for Jenkins”[1] .

持久卷配置文件(jenkins-volumn.yaml):

apiVersion: v1
kind: PersistentVolume
metadata:
  name: k8sdemo-jenkins-pv
  labels:
    app: k8sdemo-jenkins
spec:
  capacity:
    storage: 1Gi
  # volumeMode field requires BlockVolume Alpha feature gate to be enabled.
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: standard
  local:
    path: /home/vagrant/app/jenkins
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - minikube
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: k8sdemo-jenkins-pvclaim
  labels:
    app: k8sdemo-jenkins
spec:
  accessModes:
    - ReadWriteOnce
  # storageClassName: local-storage
  resources:
    requests:
      storage: 1Gi #1 GB

部署配置文件(jenkins-deployment.yaml):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: k8sdemo-jenkins-deployment
  labels:
    app: k8sdemo-jenkins
spec:
  selector:
    matchLabels:
      app: k8sdemo-jenkins
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: k8sdemo-jenkins
    spec:
      serviceAccountName: default # 服务账户的名字是default
      containers:
        - image: jenkinsci/jenkins:2.154-slim
          name: k8sdemo-jenkins-container
          imagePullPolicy: Never
          ports:
            - containerPort: 8080
            - containerPort: 50000
          volumeMounts:
            - name: k8sdemo-jenkins-persistentstorage
              mountPath: /var/jenkins_home
      volumes:
        - name: k8sdemo-jenkins-persistentstorage
          persistentVolumeClaim:
            claimName: k8sdemo-jenkins-pvclaim

注意,这里引用了服务账户“default”(serviceAccountName: default)。

服务配置文件(jenkins-service.yaml):

apiVersion: v1
kind: Service
metadata:
  name: k8sdemo-jenkins-service
  labels:
    app: k8sdemo-jenkins
spec:
  type: NodePort
  selector:
    app: k8sdemo-jenkins
  ports:
    - port: 8080
      name: http
      protocol : TCP
      nodePort: 30080
      targetPort: 8080
    - port: 50000
      name: agent
      protocol: TCP
      targetPort: 50000

这里面的一个关键点是部署和服务都暴露了两个容器端口,一个是 8080,另一个是 50000. “8080”是外部访问 Jenkins 的端口,“50000”是 Jenkins 内部集群之间的互相通信端口。这里的 Jenkins 集群不需要你搭建,而是 Jenkins 根据需要自动生成的,因此这两个端口是必须配置的。这里的配置命令都是比较标准的 k8s 配置,因此没有详细解释。

如果你想了解 k8s 命令详情(包括 Vagrant 配置),请参阅“通过搭建 MySQL 掌握 k8s(Kubernetes)重要概念(上):网络与持久卷”.

运行下面命令创建 Jenkins:

kubectl apply -f jenkins-volume.yaml
kubectl apply -f jenkins-deployment.yaml
kubectl apply -f jenkins-service.yaml

验证安装:

获得 Jenkins 的 Pod 名

vagrant@ubuntu-xenial:~$ kubectl get pod
NAME                                           READY   STATUS    RESTARTS   AGE
envar-demo                                     1/1     Running   15         27d
k8sdemo-backend-deployment-6b99dc6b8c-tbl4v    1/1     Running   7          11d
k8sdemo-database-deployment-578fc88c88-mm6x8   1/1     Running   9          16d
k8sdemo-jenkins-deployment-675dd574cb-bt7rx    1/1     Running   2          24h

查看 Jenkins 日志

vagrant@ubuntu-xenial:~$ kubectl logs k8sdemo-jenkins-deployment-675dd574cb-bt7rx

Running from: /usr/share/jenkins/jenkins.war
webroot: EnvVars.masterEnvVars.get("JENKINS_HOME")
Nov 02, 2019 1:33:30 AM org.eclipse.jetty.util.log.Log initialized
INFO: Logging initialized @3749ms to org.eclipse.jetty.util.log.JavaUtilLog

。。。

INFO: Invalidating Kubernetes client: kubernetes null
Nov 02, 2019 1:35:50 AM hudson.WebAppMain$3 run
INFO: Jenkins is fully up and running
--> setting agent port for jnlp
--> setting agent port for jnlp... done

当看到“INFO: Jenkins is fully up and running”,就说明 Jenkins 已经运行好了,Jenkins 的第一次启动需要一定时间,要耐心等待。

4. 登录

前面已经讲过,你可以在 Vagrant 里设置宿主机和虚机之间的网络互访,我的虚机的地址是“192.168.50.4”,“30080”是 Jenkins 服务的 NodePort 的对外地址,因此可以用“http://192.168.50.4:30080/” 访问 Jenkins。

登录之前先要获得初始口令,你可以在 Jenkins 的“secretsinitialAdminPassword”目录里获得管理员用户初始口令,我挂载 Jenkins 的宿主机目录是“E:app2kubappjenkins”, 因此口令文件是“E:app2kubappjenkinssecretsinitialAdminPassword”。口令是“072d7157c090479195e0acaa97bc1049”。第一次登录之后,需要重新设置用户和口令。

5. 安装推荐插件

登录之后,先要安装必要的插件才能完成整个安装工程, 直接选“Install suggested plugins”就可以了。

6 安装 Kubernetes Plugin

用管理员账户登录 Jenkins 主页面后,找到 Manage Jenkins-》Manage Plugins-》Available, 勾选安装“Kubernetes plugin”即可。

下图是安装之后的图:

plugin

7. 配置 Kubernetes Plugin

用管理员账户登录 Jenkins Master 主页面后,找到 Manage Jenkins-》Configure System-》,然后配置 Kubernetes Plugin。如下图所示:

配置

这是最重要的一个配置,决定整个安装的成败。默认的“name”是“Kubernetes“,这个不需要修改,但以后配置 Pipelines 时要用到。“Kubernetes URL”用 “https://kubernetes.default” 就可以了。设置之后点击“Test Connection”,见到“Connection test successful”就成功了。

“Jenkins URL”是从外部(从虚拟机而不是宿主机)访问 Jenkins 的地址。你可以用如下命令,找到 Kubernetes 的“Jenkins Url”:

vagrant@ubuntu-xenial:~$  sudo minikube service k8sdemo-jenkins-service  --url
http://10.0.2.15:30080
http://10.0.2.15:32289

另外一个参数是“Jenkins tunnel”,这个参数是 Jenkins Master 和 Jenkins Slave 之间通信必须配置的,但不知道为什么,网上的很多文章都没提这个参数,也许是 Jenkins 的版本不同,有些版本可能不需要。

查看容器名

vagrant@ubuntu-xenial:~$ docker ps -a
CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS                      PORTS               NAMES
d15e30169568        f793ea0abe00            "/sbin/tini -- /usr/…"   15 minutes ago      Up 15 minutes                                   k8s_k8sdemo-jenkins-container_k8sdemo-jenkins-deployment-675dd574cb-2thn2_default_fb10e438-0231-4fd2-8dbd-d9e2f0bb9d09_0

查看容器地址:

vagrant@ubuntu-xenial:~$ docker inspect d15e |grep _8080
                "K8SDEMO_JENKINS_SERVICE_PORT_8080_TCP_ADDR=10.100.3.79",
                "K8SDEMO_JENKINS_SERVICE_PORT_8080_TCP=tcp://10.100.3.79:8080",
                "K8SDEMO_JENKINS_SERVICE_PORT_8080_TCP_PROTO=tcp",
                "K8SDEMO_JENKINS_SERVICE_PORT_8080_TCP_PORT=8080",

根据上面信息,Jenkins 的地址是“tcp://10.100.3.79:8080”,把 8080 换成 50000 就可以了。最终结果是“10.100.3.79:50000”,注意不要添加“http”。

8. 测试 Jenkins:

现在 Jenkins 已经全部安装好了, 下面进行测试。在 Jenkins 主页面点击“New Item”创建新项目,如下图所示,输入项目名,然后选择“Pipeline”。

Jenkins

进入项目配置页面,如下图所示,脚本文件是 jenkinsfile-test:

test

这是最简单的测试,它直接使用 Jenkins 主节点(主节点名是 master),不需要启动子节点,因此基本上都不会有什么问题。在 Jenkins 主页面选项目“test”,然后选“Build Now”运行项目,再到“Console Output”中查看结果如下:

Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline[Pipeline] podTemplate[Pipeline] {[Pipeline] nodeRunning on Jenkins in /var/jenkins_home/workspace/test
[Pipeline] {[Pipeline] stage[Pipeline] { (Run shell)[Pipeline] sh+ echo hello world.
hello world.
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] }
[Pipeline] // podTemplate
[Pipeline] End of Pipeline
Finished: SUCCESS

9. 测试子节点:

这是复杂一点的测试,需要启动子节点,这个才能真正检测出安装的成败。先创建一个新的项目“slave-test”。

def POD_LABEL = "testpod-${UUID.randomUUID().toString()}"
podTemplate(label: POD_LABEL, cloud: 'kubernetes', containers: [
    containerTemplate(name: 'build', image: 'jfeng45/k8sdemo-backend:1.0', ttyEnabled: true, command: 'cat'),
    containerTemplate(name: 'run', image: 'jfeng45/k8sdemo-backend:1.0', ttyEnabled: true, command: 'cat')
  ]) {

    node(POD_LABEL) {
        stage('build a go project') {
            container('build') {
                stage('Build a go project') {
                    sh 'echo hello'
                }
            }
        }

        stage('Run a Golang project') {
            container('run') {
                stage('Run a Go project') {
                    sh '/root/main.exe'
                }
            }
        }

    }
}

上面是脚本(jenkins-salve-test)。其中“POD_LABEL”取任何名字都可以(在 Kubernetes-plugin 1.17.0 版本之后,系统会自动命名,但以前需要自己取名),“cloud: 'kubernetes'”要与前面定义的“Kubernetes Plugin” 相匹配。它有两个 stage,一个是“build”,另一个是“run”。在“podTemplate”里定义了每一个 stage 的镜像(这样后面的 stage 脚本里就可以引用),这里为了简便把两个镜像设成是一样的。因为是测试,第一个 stage 只是输出“echo hello”, 第二个运行镜像“jfeng45/k8sdemo-backend:1.0”里的 main.exe 程序。

在 Jenkins 主页面选项目“slave-test”,然后选“Build Now”运行项目,再到“Console Output”中查看结果如下:

Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline[Pipeline] podTemplate[Pipeline] {[Pipeline] node
Still waiting to schedule task
‘testpod-f754a7a4-6883-4be0-ba4f-c693906041ae-fjwqs-kbb7l’ is offline

Agent testpod-f754a7a4-6883-4be0-ba4f-c693906041ae-fjwqs-kbb7l is provisioned from template Kubernetes Pod Template
Agent specification [Kubernetes Pod Template] (testpod-f754a7a4-6883-4be0-ba4f-c693906041ae):
* [build] jfeng45/k8sdemo-backend:1.0
* [run] jfeng45/k8sdemo-backend:1.0

Running on testpod-f754a7a4-6883-4be0-ba4f-c693906041ae-fjwqs-kbb7l in /home/jenkins/workspace/slave-test
[Pipeline] {
[Pipeline] stage[Pipeline] { (build a go project)
[Pipeline] container[Pipeline] {
[Pipeline] stage
[Pipeline] { (Build a go project)[Pipeline] sh
+ echo heollo
heollo

[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // container
[Pipeline] }

[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Run a Golang project)[Pipeline] container
[Pipeline] {[Pipeline] stage[Pipeline] { (Run a Go project)
[Pipeline] sh
+ /root/main.exe
time="2019-11-03T01:56:59Z" level=debug msg="connect to database "
time="2019-11-03T01:56:59Z" level=debug msg="dataSourceName::@tcp(:)/?charset=utf8"
time="2019-11-03T01:56:59Z" level=debug msg="FindAll()"
time="2019-11-03T01:56:59Z" level=debug msg="user registere failed:dial tcp :0: connect: connection refused"

[Pipeline] }
[Pipeline] // stage

[Pipeline] }
[Pipeline] // container
[Pipeline] }

[Pipeline] // stage
[Pipeline] }
[Pipeline] // node

[Pipeline] }
[Pipeline] // podTemplate

[Pipeline] End of Pipeline
Finished: SUCCESS

运行成功,测试阶段就完成了。

用脚本来写 Pipeline 有两种方法,“Scripted Pipleline”和“Declarative Pipleline”,这里用的是第一种方法。详情请见“Using a Jenkinsfile[2]”. “Declarative Pipleline”是新的方法,我在以后的文章里会讲到。这里因为是测试,只要通过了就行。

不必须的安装步骤:

还有些安装步骤在某些文章中提到了,但它们只是锦上添花,不是必须的。如果你的配置出现了问题,不要怀疑是这些步骤没执行造成的。

  1. 配置名空间(namespace):

有些安装步骤为 Jenkins 配置了单独的名空间,这样当然更好,但你即使没有配置也不会出现问题。

  1. Kubernetes server certificate key:

有些安装步骤提到要配置“Kubernetes server certificate key ”,但我并没有设置它,也没有影响运行。

第二部分:常见问题

1. Jenkins 版本不对

最开始用的是 jenkins:2.60.3-alpine(这个已经是 Jenkins 镜像的最高版本了),这个版本太低,在安装插件时基本上都不成功,如下图

version

后来换成 jenkins:latest,这个应该是最新的吧,结果版本还是一样的,只不过 Linux 不是 Apline 的。

后来终于明白了是镜像错了(而不是版本的问题),是要用 Jenkinsci, 而不是 Jenkins。我用了当时排在第一位的 jenkinsci/jenkins:2.150.1-slim,安装之后,上面的插件错误全部消失了,真不容易。

2. 不支持 Kubernetes Plugin

当安装 Kubernetes Plugin 插件时,提示需要 2.150.3(我的是 2.150.1),这也太坑了。只好再次重装,这次用的是 jenkinsci/jenkins:2.154-slim,还好终于成功了。不过这个其实还是以前的镜像,最新的在“jenkins/jenkins”。

3. 不能访问 Kubernetes

错误信息如下:

Forbidden!Configured service account doesn't have access. Service account may have been revoked. User "system:serviceaccount:default:default" cannot get services in the namespace "default"

详情请参见Kubernetes log, User “system:serviceaccount:default:default” cannot get services in the namespace[3]。

错误原因是没有建立 service account。解决办法是先创建“service-account.yaml”文件,然后运行如下命令:

kubectl create clusterrolebinding service-reader-pod --clusterrole=service-reader  --serviceaccount=default:default

再次运行,错误消失。

4. Jenkins URL 地址不对

在 Jenkins 主页面,进入 Manage Jenkins-》System Log-》All Jenkins Logs, 错误信息如下。

SEVERE: http://192.168.50.4:30080/ provided port:50000 is not reachable
java.io.IOException: http://192.168.50.4:30080/ provided port:50000 is not reachable
     at org.jenkinsci.remoting.engine.JnlpAgentEndpointResolver.resolve(JnlpAgentEndpointResolver.java:303)
     at hudson.remoting.Engine.innerRun(Engine.java:527)
     at hudson.remoting.Engine.run(Engine.java:488)

这个错误主要是和 Kubernetes-plugin 配置有关。在 Jenkins 主页面,进入 Manage Jenkins-》Configure System》,在“http://192.168.50.4:30080/configure” 里有两个“Jenkins URL”,不要弄混了。第一个是“Jenkins Location”下的“Jenkins URL”, 它是宿主机访问 Jenkins 的地址。

host

第二个是“Cloud”下的“Jenkins URL”, 它是从虚拟机访问 Jenkins 的地址。

jenkins

在上图中,我开始时用的是“http://192.168.50.4:30080/” ,但这个是从宿主机访问 Jenkins 的 Url,不是从虚机内部访问的 Url。你可以用如下命令,找到 Kubernetes 的“Jenkins Url”

vagrant@ubuntu-xenial:~$  sudo minikube service k8sdemo-jenkins-service  --url
http://10.0.2.15:30080
http://10.0.2.15:32289

键入如下命令测试 URL。

vagrant@ubuntu-xenial:~$ curl http://10.0.2.15:30080
<html><head><meta http-equiv='refresh' content='1;url=/login?from=%2F'/><script>window.location.replace('/login?from=%2F');</script></head><body style='background-color:white; color:white;'>

Authentication required
<!--
You are authenticated as: anonymous
Groups that you are in:

Permission you need to have (but didn't): hudson.model.Hudson.Read
... which is implied by: hudson.security.Permission.GenericRead
... which is implied by: hudson.model.Hudson.Administer
-->

</body></html>

这就说明 URL 是好的。

5. 不能连接 slave

“Jenkins Url”改了之后,地址是对的,但还是不通。运行项目时,页面显示如下信息:

“Console Output”(在 Jenkins->salve-test->#13 中,其中#13 是 build #)显示如下信息:

Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline[Pipeline] podTemplate[Pipeline] {[Pipeline] node
Still waiting to schedule task
‘testpod-d56038a0-45a2-41d1-922d-2879e3610900-0hr0m-sfv8s’ is offline

后来发现还有一个参数要填写,就是“Jenkins tunnel”。如下图所示。

file

详情请见 Kubernetes Jenkins plugin - slaves always offline[4].

填写之后原来的信息没有了,而且出现了“Agent discovery successful”,这个信息是原来没有的。但又有新的错误。可用如下方法查看系统日志,在 Jenkins 主页面,选择 Manage Jenkins-》System Log-》All Jenkins Logs, 信息是这样的:

INFO: Agent discovery successful
  Agent address: http://10.0.2.15
  Agent port:    50000
  Identity:      3e:1b:5f:48:f7:5b:f8:6d:ea:49:1d:b9:44:9a:2f:6c
Oct 30, 2019 12:18:51 AM hudson.remoting.jnlp.Main$CuiListener status
INFO: Handshaking
Oct 30, 2019 12:18:51 AM hudson.remoting.jnlp.Main$CuiListener status
INFO: Connecting to http://10.0.2.15:50000
Oct 30, 2019 12:18:51 AM hudson.remoting.jnlp.Main$CuiListener error
SEVERE: null
java.nio.channels.UnresolvedAddressException
     at sun.nio.ch.Net.checkAddress(Net.java:101)
     at sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:622)
     at java.nio.channels.SocketChannel.open(SocketChannel.java:189)
     at org.jenkinsci.remoting.engine.JnlpAgentEndpoint.open(JnlpAgentEndpoint.java:203)
     at hudson.remoting.Engine.connectTcp(Engine.java:678)
     at hudson.remoting.Engine.innerRun(Engine.java:556)
     at hudson.remoting.Engine.run(Engine.java:488)

它的原因是“JenkinsTunnel”的地址还是不对,可用如下方法找到“Jenkins tunnel”地址:

vagrant@ubuntu-xenial:~$ docker ps -a
CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS                      PORTS               NAMES
d15e30169568        f793ea0abe00            "/sbin/tini -- /usr/…"   15 minutes ago      Up 15 minutes                                   k8s_k8sdemo-jenkins-container_k8sdemo-jenkins-deployment-675dd574cb-2thn2_default_fb10e438-0231-4fd2-8dbd-d9e2f0bb9d09_0

vagrant@ubuntu-xenial:~$ docker inspect d15e |grep _8080
                "K8SDEMO_JENKINS_SERVICE_PORT_8080_TCP_ADDR=10.100.3.79",
                "K8SDEMO_JENKINS_SERVICE_PORT_8080_TCP=tcp://10.100.3.79:8080",
                "K8SDEMO_JENKINS_SERVICE_PORT_8080_TCP_PROTO=tcp",
                "K8SDEMO_JENKINS_SERVICE_PORT_8080_TCP_PORT=8080",

根据上面信息,Jenkins 容器地址是“tcp://10.100.3.79:8080”,把 8080 换成 50000 就可以了。最终结果是“10.100.3.79:50000”,注意不要添加“http”。详情请见 What setting to use for jenkins tunnel?[5]

6. 镜像问题

当使用的镜像文件是“k8sdemo-backend:latest”或“k8sdemo-backend:1.0”时,“Console Output”显示错误如下:

Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline[Pipeline] podTemplate[Pipeline] {[Pipeline] nodeStill waiting to schedule task
All nodes of label ‘testpod-2971e0ce-e023-475f-b0ec-6118c5699188’ are offline
Aborted by admin[Pipeline] // node
[Pipeline] }
[Pipeline] // podTemplate
[Pipeline] End of Pipeline
Finished: ABORTED

查看 Pod, 错误是“ImagePullBackOff”:

vagrant@ubuntu-xenial:~$ kubectl get pod
NAME                                                       READY   STATUS             RESTARTS   AGE
envar-demo                                                 1/1     Running            15         28d
k8sdemo-backend-deployment-6b99dc6b8c-tbl4v                1/1     Running            7          12d
k8sdemo-database-deployment-578fc88c88-mm6x8               1/1     Running            9          17d
k8sdemo-jenkins-deployment-675dd574cb-bt7rx                1/1     Running            2          2d
testpod-2971e0ce-e023-475f-b0ec-6118c5699188-xwwqq-vv59p   2/3     ImagePullBackOff   0          38s

查看镜像:

vagrant@ubuntu-xenial:~$ docker image ls
REPOSITORY                                TAG                 IMAGE ID            CREATED             SIZE
jfeng45/k8sdemo-backend                   1.0                 f48d362fdebf        11 days ago         14.4MB
k8sdemo-backend                           1.0                 f48d362fdebf        11 days ago         14.4MB
k8sdemo-backend                           latest              f48d362fdebf        11 days ago         14.4MB

这里一共有三个“k8sdemo-backend”镜像,它们的“Image ID”都是一样的,之所以有三个是因为我用如下命令创建了 tag

docker tag k8sdemo-backend jfeng45/k8sdemo-backend:1.0

但创建了之后,就只有“jfeng45/k8sdemo-backend:1.0”(最晚创建的)能够用在 Jenkins 的 Pipeline 脚本里,其他两个都会报错。修改了正确的镜像文件之后就运行成功了。

7. pv 和 pvc 删除慢

当用以下命令删除 pv 时,命令迟迟不能返回。

kubectl delete pv k8sdemo-jenkins-pv

当你查看时,状态(status)显示一直是“Terminating”,但总是不能结束退出。pvc 也是一样。

vagrant@ubuntu-xenial:~$ kubectl get pv
NAME                  CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS        CLAIM                              STORAGECLASS   REASON   AGE
k8sdemo-backend-pv    1Gi        RWO            Retain           Bound         default/k8sdemo-backend-pvclaim    standard                13d
k8sdemo-database-pv   1Gi        RWO            Retain           Bound         default/k8sdemo-database-pvclaim   standard                18d
k8sdemo-jenkins-pv    1Gi        RWO            Retain           Terminating   default/k8sdemo-jenkins-pvclaim    standard                6d8h

这个主要原因是用到它们的服务和部署还在运行,先把服务和部署删除之后,pv 和 pvc 的删除操作就马上结束,顺利返回了。

源码:

完整源码的 github 链接[6]

注意,本文的程序在 0.1(tag)下,这个程序的主分支以后还会修改。

下面是程序的目录结构,黄色部分是与本文有关的配置文件。

config

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值