本文将介绍 jenkins 在kubernetes下的CI/CD发布流程。
以下内容仅是个人测试所用,如有不妥,可在下方评论留言,评论可能需要科学上网😂。
jenkins在k8s下,可以实现节点自动调度,无需关注节点,且可以直接使用内部的api-server
使用helm安装jenkins
-
创建一个pv,因为容器是无状态的,需要创建一个volume,用于存放jenkins的一些配置信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
kind: PersistentVolume apiVersion: v1 metadata: name: jenkins-pv spec: # selector: # jenkins: master # storageClassName: jenkins-pv capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain hostPath: path: "/data/jenkins"
-
使用helm安装jenkins
1
helm install --name jenkins stable/jenkins
如果开启了rbac可以使用
helm upgrade jenkins stable/jenkins --set rbac.install=true
进行更新,无需重新安装。 -
配置ingress(可选)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: jenkins labels: app: jenkins annotations: kubernetes.io/ingress.class: nginx namespace: default spec: rules: - host: jenkins.192.168.2.20.xip.io http: paths: - backend: serviceName: jenkins servicePort: 80 path: /
使用
kubectl create -f jenkins-ingress.yaml
创建。 -
到此,jenkins基本安装完成,如果出现问题,可以使用kubectl describe 和kubectl logs进行排查。
配置jenkins
- 安装完成过后,默认jenkins的系统配置下会自动添加好kubernetes cloud,如下图
以及配置好的kubernetes pod template
-
因为待会儿会将程序部署到k8s中,所以还需要安装
kubernetes-cd
👉官方介绍,直接在插件管理搜索下载即可。 -
代码及配置地址,👉github
-
配置好待会儿会用到的docker hub的账号密码及k8s的kubeconfig
开始一个新项目
创建一个pipeline任务,👉官方介绍
这里使用一个golang写的demo web,在docker容器中进行构建。
jenkins会自动在k8s集群中创建自己的slave及docker容器,然后把挂载工作目录,完成后自动销毁刚刚创建的容器。
在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 48 49 50
def label = "mypod-${UUID.randomUUID().toString()}" podTemplate(label: label, containers: [ containerTemplate(name: 'docker', image: 'docker:18.06.2', command: 'cat', ttyEnabled: true)], volumes: [ hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')] ) { node(label) { def ImageName = 'zouhl/demo-app' stage('Get a Golang project'){ git url: 'https://github.com/zou2699/demoweb.git' container('docker') { stage('Build a Go project and docker image') sh """ docker build -t ${ImageName}:${BUILD_NUMBER} . """ stage('push image') { withDockerRegistry(credentialsId: 'dockerHub') { sh """ docker push ${ImageName}:${BUILD_NUMBER} """ } } stage('Remove the local docker image') { sh "docker rmi ${ImageName}:${BUILD_NUMBER}" } } } stage('deploy to k8s') { kubernetesDeploy( kubeconfigId: 'kubeconfig', configs: 'deploy.yaml', enableConfigSubstitution: true, ) } stage('create or update ingress') { input 'Ready to go?' kubernetesDeploy( kubeconfigId: 'kubeconfig', configs: 'app-ingress.yaml', enableConfigSubstitution: true, ) } } }
其中deploy.yaml的镜像名字填写为自己的hub地址。
进行构建
下面是构建和部署的过程,仅作参考。
Started by user admin
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] podTemplate
[Pipeline] {
[Pipeline] node
Still waiting to schedule task
‘jenkins-slave-5qg6x-q2mst’ is offline
Agent jenkins-slave-5qg6x-q2mst is provisioned from template Kubernetes Pod Template
Agent specification [Kubernetes Pod Template] (mypod-c92aa982-03f8-42f1-857e-66cca7e96e21):
* [docker] docker:18.06.2
Running on jenkins-slave-5qg6x-q2mst in /home/jenkins/workspace/demo-app
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Get a Golang project)
[Pipeline] git
Cloning the remote Git repository
Cloning repository https://github.com/zou2699/demoweb.git
> git init /home/jenkins/workspace/demo-app # timeout=10
Fetching upstream changes from https://github.com/zou2699/demoweb.git
> git --version # timeout=10
> git fetch --tags --progress https://github.com/zou2699/demoweb.git +refs/heads/*:refs/remotes/origin/*
> git config remote.origin.url https://github.com/zou2699/demoweb.git # timeout=10
> git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
> git config remote.origin.url https://github.com/zou2699/demoweb.git # timeout=10
Fetching upstream changes from https://github.com/zou2699/demoweb.git
> git fetch --tags --progress https://github.com/zou2699/demoweb.git +refs/heads/*:refs/remotes/origin/*
Checking out Revision fbe3e5c71305b8ec166e84f47b3e824235119570 (refs/remotes/origin/master)
Commit message: "add ingress file"
[Pipeline] container
[Pipeline] {
[Pipeline] stage (Build a Go project and docker image)
Using the ‘stage’ step without a block argument is deprecated
Entering stage Build a Go project and docker image
Proceeding
[Pipeline] sh
> git rev-parse refs/remotes/origin/master^{commit} # timeout=10
> git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10
> git config core.sparsecheckout # timeout=10
> git checkout -f fbe3e5c71305b8ec166e84f47b3e824235119570
> git branch -a -v --no-abbrev # timeout=10
> git checkout -b master fbe3e5c71305b8ec166e84f47b3e824235119570
> git rev-list --no-walk 561c05039df2a1a5effd018139b244d2f23fa427 # timeout=10
+ docker build -t zouhl/demo-app:20 .
Sending build context to Docker daemon 11.29MB
Step 1/9 : FROM golang:1.11
1.11: Pulling from library/golang
741437d97401: Already exists
34d8874714d7: Already exists
0a108aa26679: Already exists
7f0334c36886: Already exists
d35724ed4672: Already exists
e75ac00960df: Already exists
08686fc4280e: Already exists
Digest: sha256:baee78b46407117bfa6777b1e0361018b503936f9f878a4e18068207a0a1ff0c
Status: Downloaded newer image for golang:1.11
---> 901414995ecd
Step 2/9 : WORKDIR /go/src/app
---> Running in bd8a5da92ec0
Removing intermediate container bd8a5da92ec0
---> 664736b487fb
Step 3/9 : COPY . .
---> b1e7198a6d62
Step 4/9 : RUN go get -d -v ./...
---> Running in 1258cae3f35b
Removing intermediate container 1258cae3f35b
---> e6c54702bfe2
Step 5/9 : RUN go install -v ./...
---> Running in 0e5dfd0fa037
[91mapp/vendor/github.com/gin-gonic/gin/json
[0m[91mapp/vendor/github.com/golang/protobuf/proto
[0m[91mapp/vendor/github.com/gin-contrib/sse
[0m[91mapp/vendor/github.com/ugorji/go/codec
[0m[91mapp/vendor/gopkg.in/go-playground/validator.v8
[0m[91mapp/vendor/gopkg.in/yaml.v2
[0m[91mapp/vendor/github.com/mattn/go-isatty
[0m[91mapp/vendor/github.com/gin-gonic/gin/render
[0m[91mapp/vendor/github.com/gin-gonic/gin/binding
[0m[91mapp/vendor/github.com/gin-gonic/gin
[0m[91mapp
[0mRemoving intermediate container 0e5dfd0fa037
---> fc1ea9025ea4
Step 6/9 : RUN rm -rf /go/src/*
---> Running in db72fd36ca53
Removing intermediate container db72fd36ca53
---> 717e4dca6895
Step 7/9 : WORKDIR /go/bin/
---> Running in 037344a8cdb6
Removing intermediate container 037344a8cdb6
---> 9acf3bd33867
Step 8/9 : EXPOSE 8080
---> Running in cdf47489216b
Removing intermediate container cdf47489216b
---> 14a9757f4895
Step 9/9 : CMD ["./app"]
---> Running in 072100ec5df1
Removing intermediate container 072100ec5df1
---> a9587242060c
Successfully built a9587242060c
Successfully tagged zouhl/demo-app:20
[Pipeline] stage
[Pipeline] { (push image)
[Pipeline] withDockerRegistry
Executing shell script inside container [docker] of pod [jenkins-slave-5qg6x-q2mst]
Executing command: "docker" "login" "-u" "zouhl" "-p" ******** "https://index.docker.io/v1/"
exit
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Login Succeeded
WARNING! Your password will be stored unencrypted in /home/jenkins/workspace/demo-app@tmp/924b09bb-ba00-4377-8a9b-42e0fe5173e1/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
[Pipeline] {
[Pipeline] sh
+ docker push zouhl/demo-app:20
The push refers to repository [docker.io/zouhl/demo-app]
fb0291753bf7: Preparing
c1ff945be567: Preparing
a5e3559c27d7: Preparing
06a8ef03aeff: Preparing
c5472925cd03: Preparing
77afbe0f4fed: Preparing
c3b2e3d53ad1: Preparing
77b4b6493272: Preparing
6257fa9f9597: Preparing
578414b395b9: Preparing
abc3250a6c7f: Preparing
13d5529fd232: Preparing
77b4b6493272: Waiting
6257fa9f9597: Waiting
578414b395b9: Waiting
abc3250a6c7f: Waiting
13d5529fd232: Waiting
77afbe0f4fed: Waiting
c3b2e3d53ad1: Waiting
c5472925cd03: Pushed
fb0291753bf7: Pushed
a5e3559c27d7: Pushed
c3b2e3d53ad1: Layer already exists
77afbe0f4fed: Layer already exists
06a8ef03aeff: Pushed
77b4b6493272: Layer already exists
578414b395b9: Layer already exists
6257fa9f9597: Layer already exists
abc3250a6c7f: Layer already exists
13d5529fd232: Layer already exists
c1ff945be567: Pushed
20: digest: sha256:40a10e22a19508962ef00e469db4091e29633460e56e9e8e4b958e9abf1beff4 size: 2838
[Pipeline] }
[Pipeline] // withDockerRegistry
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (Remove the local docker image)
[Pipeline] sh
+ docker rmi zouhl/demo-app:20
Untagged: zouhl/demo-app:20
Untagged: zouhl/demo-app@sha256:40a10e22a19508962ef00e469db4091e29633460e56e9e8e4b958e9abf1beff4
Deleted: sha256:a9587242060c6de1a06293900c04b94e1937b3748f238d377f80a147635ea42c
Deleted: sha256:14a9757f4895ad23bded56d5df14c9b7c5d1a4f2b882ae9da0c4e9d27986a5ea
Deleted: sha256:9acf3bd338676f2772f72a55bebd8a30c27156a88ab0c4115292d24a527b33aa
Deleted: sha256:717e4dca6895a79899982af59313840ddfe321d7217ca00400864aa2bfd2ef19
Deleted: sha256:aeaba5e7b1eeda47794f51e53b5c8435d87aec22dfa04e221fb029ac8840263c
Deleted: sha256:fc1ea9025ea4cab736ae85d3c23de6294dcff7cb9509f19b57265b9c4d5121bc
Deleted: sha256:94209db674e4f9dc6bd2f561cfc9c2c5dac2796a1d82e3e662abc24e11177efb
Deleted: sha256:e6c54702bfe235a25c3b94383dc93e2ff61e67d2aeb9eb870710ae9767d0f990
Deleted: sha256:70c2e14e69370aae7df8921073263454e4945174223b6b2bb427d4474b0a8e4b
Deleted: sha256:b1e7198a6d629bacd799a4fc0c6df060da05f8d953c6ecb6ab44b73df78db959
Deleted: sha256:ded0ab7056905293366361f3ccef519fdf97e9660c80a8fcbe74cbab5fa5779c
Deleted: sha256:664736b487fbbe1d83938e23061060ee8fe863c4a64bc49273e4609cb91cca11
Deleted: sha256:52a27f01339de7babc1c83e843cb76b7ee68e5f438f562e1ee6c61cd0822d1ae
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // container
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (deploy to k8s)
[Pipeline] kubernetesDeploy
Starting Kubernetes deployment
Loading configuration: /home/jenkins/workspace/demo-app/deploy.yaml
Finished Kubernetes deployment
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (create or update ingress)
[Pipeline] input
Ready to go?
Proceed or Abort
Applied Deployment: Deployment(apiVersion=extensions/v1beta1, kind=Deployment, metadata=ObjectMeta(annotations=null, clusterName=null, creationTimestamp=2019-03-03T07:40:41Z, deletionGracePeriodSeconds=null, deletionTimestamp=null, finalizers=[], generateName=null, generation=4, initializers=null, labels={run=demo-web}, name=demo-web, namespace=default, ownerReferences=[], resourceVersion=399848, selfLink=/apis/extensions/v1beta1/namespaces/default/deployments/demo-web, uid=a54a482c-3d87-11e9-b446-000c29c5c464, additionalProperties={}), spec=DeploymentSpec(minReadySeconds=null, paused=null, progressDeadlineSeconds=2147483647, replicas=2, revisionHistoryLimit=2147483647, rollbackTo=null, selector=LabelSelector(matchExpressions=[], matchLabels={run=demo-web}, additionalProperties={}), strategy=DeploymentStrategy(rollingUpdate=RollingUpdateDeployment(maxSurge=IntOrString(IntVal=1, Kind=null, StrVal=null, additionalProperties={}), maxUnavailable=IntOrString(IntVal=1, Kind=null, StrVal=null, additionalProperties={}), additionalProperties={}), type=RollingUpdate, additionalProperties={}), template=PodTemplateSpec(metadata=ObjectMeta(annotations=null, clusterName=null, creationTimestamp=null, deletionGracePeriodSeconds=null, deletionTimestamp=null, finalizers=[], generateName=null, generation=null, initializers=null, labels={run=demo-web}, name=null, namespace=null, ownerReferences=[], resourceVersion=null, selfLink=null, uid=null, additionalProperties={}), spec=PodSpec(activeDeadlineSeconds=null, affinity=null, automountServiceAccountToken=null, containers=[Container(args=[], command=[], env=[], envFrom=[], image=zouhl/demo-app:20, imagePullPolicy=Always, lifecycle=null, livenessProbe=null, name=demo-web, ports=[ContainerPort(containerPort=80, hostIP=null, hostPort=null, name=null, protocol=TCP, additionalProperties={})], readinessProbe=null, resources=ResourceRequirements(limits=null, requests=null, additionalProperties={}), securityContext=null, stdin=null, stdinOnce=null, terminationMessagePath=/dev/termination-log, terminationMessagePolicy=File, tty=null, volumeMounts=[], workingDir=null, additionalProperties={})], dnsPolicy=ClusterFirst, hostAliases=[], hostIPC=null, hostNetwork=null, hostPID=null, hostname=null, imagePullSecrets=[], initContainers=[], nodeName=null, nodeSelector=null, restartPolicy=Always, schedulerName=default-scheduler, securityContext=PodSecurityContext(fsGroup=null, runAsNonRoot=null, runAsUser=null, seLinuxOptions=null, supplementalGroups=[], additionalProperties={}), serviceAccount=null, serviceAccountName=null, subdomain=null, terminationGracePeriodSeconds=30, tolerations=[], volumes=[], additionalProperties={}), additionalProperties={}), additionalProperties={}), status=DeploymentStatus(availableReplicas=2, collisionCount=null, conditions=[DeploymentCondition(lastTransitionTime=2019-03-03T07:53:53Z, lastUpdateTime=2019-03-03T07:53:53Z, message=Deployment has minimum availability., reason=MinimumReplicasAvailable, status=True, type=Available, additionalProperties={})], observedGeneration=3, readyReplicas=2, replicas=2, unavailableReplicas=null, updatedReplicas=2, additionalProperties={}), additionalProperties={})
Applied Service: Service(apiVersion=v1, kind=Service, metadata=ObjectMeta(annotations=null, clusterName=null, creationTimestamp=2019-03-03T07:40:41Z, deletionGracePeriodSeconds=null, deletionTimestamp=null, finalizers=[], generateName=null, generation=null, initializers=null, labels=null, name=demo-web, namespace=default, ownerReferences=[], resourceVersion=396922, selfLink=/api/v1/namespaces/default/services/demo-web, uid=a5673bb9-3d87-11e9-b446-000c29c5c464, additionalProperties={}), spec=ServiceSpec(clusterIP=10.1.115.186, externalIPs=[], externalName=null, externalTrafficPolicy=Cluster, healthCheckNodePort=null, loadBalancerIP=null, loadBalancerSourceRanges=[], ports=[ServicePort(name=null, nodePort=31907, port=80, protocol=TCP, targetPort=IntOrString(IntVal=80, Kind=null, StrVal=null, additionalProperties={}), additionalProperties={})], selector={run=demo-web}, sessionAffinity=None, type=NodePort, additionalProperties={}), status=ServiceStatus(loadBalancer=LoadBalancerStatus(ingress=[], additionalProperties={}), additionalProperties={}), additionalProperties={})
Approved by admin
[Pipeline] kubernetesDeploy
Starting Kubernetes deployment
Finished Kubernetes deployment
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] }
[Pipeline] // podTemplate
[Pipeline] End of Pipeline
Finished: SUCCESS
Page
总结
流程说明
应用构建和发布流程说明。
- 用户向Gitlab提交代码,代码中必须包含
Dockerfile
- 将代码提交到远程仓库
- 用户在发布应用时需要填写git仓库地址和分支、服务类型、服务名称、资源数量、实例个数,确定后触发Jenkins自动构建
- Jenkins的CI流水线自动编译代码并打包成docker镜像推送到Harbor镜像仓库
- Jenkins的CI流水线中包括了自定义脚本,根据我们已准备好的kubernetes的YAML模板,将其中的变量替换成用户输入的选项
- 生成应用的kubernetes YAML配置文件
- 更新Ingress的配置,根据新部署的应用的名称,在ingress的配置文件中增加一条路由信息
- Jenkins调用kubernetes的API,部署应用
这里只是一个简单的例子,可以对ingress进行优化,实现灰度发布。
代码的发布和构建只要按照步骤来,即可。
既然jenkins在k8s中,就要遵循其设计的理念,使用容器进行构建,而不是传统的ssh。