在Jenkins Pipleline中使用docker方法汇总

Just the simple way to build docker image in Jenkins pipeline on K8S?

Background

Running Jenkins job in Pod of K8S is a very popular solution for CI. In my environment, it has K8S cluster and Jeninks Kubernetes Plugin connecting to it.

By using pod template provided by that plug-in, whenever a jenkins pipeline job starts, a temporary slave pod being created for it, then destroyed automatically after job finishes.

This works pretty well. First of all, It saves resources by replying on K8S to dynamically allocate them. Moreover, to get clean and isolated environments to work, a jenkins job just need to configure containers on demand.

But things become a bit more boring when people need to build docker image. In this articale, I am listing all ways to implement it and will discuss which one is the best.

Ways to run docker cli in pipeline

Solution 1: Use host node's socket

If the k8s cluster using docker as container runtime, which means every node will have docker. All containers can use host's docker socket then.

Code example:

podTemplate(
yaml: """
apiVersion: v1
kind: Pod
spec:
  volumes:
  - name: docker-sock
    hostPath:
      path: /var/run/docker.dock
      type: File

  containers:
  - name: demo
    image: alpinelinux/docker-cli
    imagePullPolicy: Always
    env:
    - name: DOCKER_HOST
      value: unix:///var/run/docker.dock
    command: ["cat"]
    tty: true
    volumeMounts:
    - name: docker-sock
      mountPath: /var/run/docker.sock
"""
) { node(POD_LABEL) {
    container('demo') {
        sh """
            docker pull busybox
        """
    }
}}

Problems:

  • Must have docker as container runtime, which is heavy. Not work if using other runtimes e.g. containerd or cri-o.
  • Cannot customize docker version in pipeline.

Solution 2: Run docker as container (DIND)

docker:dind

Docker provides an official docker-in-docker image in docker hub. So we can use it directly in podTemplate from jenkins pipeline.

Note: To use docker-in-docker, the container need to be run as privileged.

Use docker in docker:dind

Here is the mini code need to run docker-in-docker.

podTemplate(
yaml: """
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: dind
    image: docker:dind
    securityContext:
      privileged: true
"""
) { node(POD_LABEL) {
    container('dind') {
        sh """
            docker pull busybox
        """
    }
}}

Please note --privileged is a MUST or it will not work.

securityContext:
  privileged: true

By default, the DOCKER_HOST is not set in above dind container. So when command docker... was run, it automatically connect to docker daemon by /var/run/docker.sock.

Here it is running docker command in dind container, which has the docker-daemon running in same container also.

However what if people want to run docker command from other containers in the same pod of dind?

Share dind to other containers - TCP

By default, docker::dind has dockerd-entrypoint.sh as entry-point.

The real command that script finally invoked is like following:

dockerd --host=unix:///var/run/docker.sock --host=tcp://0.0.0.0:2376 --tlsverify --tlscacert /certs/server/ca.pem --tlscert /certs/server/cert.pem --tlskey /certs/server/key.pem

So after starting up, it will be listening on tcp://0.0.0.0:2376, with tls verification enabled by default. Since all containers in pod can communicate like in same host, other containers can easily access docker-daemon by tcp://localhost:2376.

Disable TLS

TLS is boring - to connect to it the client must provide certificate. Here let's keep it simple at the beginning by removing the tls verification.

There are two ways to do it: unset environment variable or overwrite entry-point of dind.

By Environment

DOCKER_TLS_CERTDIR is being used by docker:dind to store tls cert, if set to empty, then tsl would be disabled automatically. I

In following code, the environment is set to empty and also another new container named demo, which uses alpine's docker-cli client image, is added into pod.

podTemplate(
yaml: """
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: dind
    image: docker:dind
    securityContext:
      privileged: true
    env:
    - name: DOCKER_TLS_CERTDIR
      value:
  - name: demo
    image: alpinelinux/docker-cli
    env:
    - name: DOCKER_HOST
      value: tcp://localhost:2375
    command: ["cat"]
    tty: true
"""
) { node(POD_LABEL) {
    container('demo') {
        sh """
            # use docker-daemon in dind container
            # in demo there is a docker client only
            docker pull busybox
        """
    }
}}

Note: Here the tcp listening port is changed to 2375 automatically when dind gets up !! So in demo container DOCKER_HOST must be set to tcp://localhost:2375.

By Custom Entry-Point

Remember that default entry-point of docker:dind is dockerd-entrypoint.sh, this script finally call dockerd as a service.

So instead of setting environment, replacing entry-point with dockerd plus customized parameters is easy and more portable.

podTemplate(
yaml: """
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: dind
    image: docker:dind
    securityContext:
      privileged: true
    command: ["/bin/sh","-c"]
    args: ["dockerd --host=tcp://0.0.0.0:8989"]
  - name: demo
    image: alpinelinux/docker-cli
    env:
    - name: DOCKER_HOST
      value: tcp://localhost:8989
    command: ["cat"]
    tty: true
"""
) { node(POD_LABEL) {
    container('demo') {
        sh """
            # use docker-daemon in dind container
            # in demo there is a docker client only
            docker pull busybox
        """
    }
}}

Here port 8989 is set as the docker-daemon's listen port by passing --host value to entry-point which has been replaced by dockerd command in code. Following it DOCKER_HOST in demo should be set to tcp://localhost:8989.

Share dind to other containers - Unix Socket

All containers in one pod are in same host so they can share files in the host. By mounting host's directory and put local socket file there, it is easy to have docker daemon accessible to all containers. Basically here two steps needed:

  • Mount a directory in host for all containers in host machine
  • Set host parameter pointing to socket file in above directory
def shared_mount_point="/dind-only"
def docker_host_sock = "unix://${shared_mount_point}/docker.sock"

podTemplate(cloud: 'kubernetes',
yaml: """
apiVersion: v1
kind: Pod
spec:
  volumes:
  - name: tmp
    emptyDir: {}
    
  containers:
  - name: dind
    image: docker:dind
    env:
    - name: DOCKER_HOST
      value: ${docker_host_sock}
    securityContext:
      privileged: true
    args: ["--host=${docker_host_sock}"]
    volumeMounts:
    - name: tmp
      mountPath: ${shared_mount_point}
      
  - name: demo
    image: alpinelinux/docker-cli
    env:
    - name: DOCKER_HOST
      value: ${docker_host_sock}
    command: ["cat"]
    tty: true
    volumeMounts:
    - name: tmp
      mountPath: ${shared_mount_point}
"""
) { node(POD_LABEL) {
    container('demo') {
        sh """
            docker pull busybox
        """
    }
}}

Here the code makes dind container to listen on unix-domain socket through file /dind-only/docker.sock that shared with demo container through mounting temporary directory on /dind-only .

TCP vs Unix-Domain Socket ?

There is no big difference. But local socket should have better performance because it is more like read/write local file.

Wait Docker-Daemon Up !

So far it looks pretty good. But actually there is a potential problem:

What if the dind is already up but docker-daemon not ready when the pipeline runningdocker ... command?

K8S provides start-up probe for us to solve it !

For example, to make sure docker daemon is listening on tcp port when pipeline runs into docker, just add followings into yaml ofdind container:

- name: dind
    image: docker:dind
  ...
  ...
  startupProbe:
    tcpSocket:
      port: tcp://localhost:8989
    failureThreshold: 10
    initialDelaySeconds: 5
    periodSeconds: 6

For unix-domain socket, just use stat command to check if socket file exists.

def docker_host_sock = "unix://${shared_mount_point}/docker.sock"
...
...
- name: dind
    image: docker:dind
  ...
  ...
  startupProbe:
    exec:
      command:
      - stat
      - ${docker_host_sock_file}

Security !

Running dind in privileged mode is NOT SAFE.

By replacing docker:dind by docker:dind-rootless, the dind container can run as a normal user - this makes it safer.

Note:docker:dind-rootless need --experimental option.

Summary

Docker-In-Docker is easy to run in Jenkins on K8S. The only concern is --privileged may bring security issue. Please be careful. Though in experiment phase, the docker:dind-rootless is strongly recommended at this moment for production environment.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容