天天看點

基于 Kubernetes 的 Jenkins 服務也可以去 Docker 了

基于 Kubernetes 的 Jenkins 服務也可以去 Docker 了
從原理上看,在 Kubernetes 叢集中,Jenkins 都可以使用 Podman 進行鏡像建構,本文主要以 Containerd 為例。

1. 去 Docker 給 CICD 帶來新的挑戰

在 CICD 場景下, 我們經常需要在流水線中建構和推送鏡像。

在之前的文檔 《在 Kubernetes 上動态建立 Jenkins Slave》 中, 我描述了通過挂載

/var/run/docker.sock

檔案, 允許在 Docker 驅動的 Kubernetes 叢集中建構和推送鏡像。在文檔 《如何在 Docker 中使用 Docker》中, 我又進行了更加詳細地闡述, 其原理是共享主機 Docker Daemon。

在 1.20 版本之後, Kubernetes 社群放棄了對 Docker 的支援, 而後又有其他社群接手, 隐約給 Docker 蒙上了一層陰影。在這樣的背景下, 我們開始考慮非 Docker 環境下, 如何進行 CICD 實踐。

非 Docker 環境意味着之前挂載

/var/run/docker.sock

的方式失效了, 我們需要尋找新的解決方案。

2. 測試叢集環境

2.1 Kubernetes - 1.17.9

執行如下指令, 檢視 Kubernetes 版本:

1

2

3

4

kubectl version

Client Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.9", GitCommit:"4fb7ed12476d57b8437ada90b4f93b17ffaeed99", GitTreeState:"clean", BuildDate:"2020-07-15T16:18:16Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}

Server Version: version.Info{Major:"1", Minor:"17", GitVersion:"v1.17.9", GitCommit:"4fb7ed12476d57b8437ada90b4f93b17ffaeed99", GitTreeState:"clean", BuildDate:"2020-07-15T16:10:45Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}

2.2 Containerd - 1.4.3

執行如下指令, 檢視 containerd 版本:

1

2

3

containerd --version

containerd github.com/containerd/containerd v1.4.3 269548fa27e0089a8b8278fc4fc781d7f65a939b

3. 鏡像管理工具 Podman

由于 Containerd 不支援 Docker API, 常見的

docker build

docker push

等指令在 Containerd 環境下無法使用。是以, 需要一種不依賴于 Docker, 針對 OCI 标準的鏡像建構和推送工具。

3.1 Podman 簡介

Podman 是一個實作 OCI 标準的容器和鏡像管理工具, 同時也是 Daemonless, 不需要守護程序, 也支援非特權使用者使用。Podman 提供了類似 Docker CLI 的功能, 大部分情況下可以執行

alias docker=podman

使用 Podman 替換 Docker , 而不會有任何問題。

3.2 Podman 安裝

  • 安裝 Podman 指令行工具

安裝方法可以參考 Podman 的安裝指引。這裡以 CentOS 7 為例:

1

2

curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable.repo https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/CentOS_7/devel:kubic:libcontainers:stable.repo

yum -y install podman

  • 檢視 Podman 版本

1

2

3

podman --version

podman version 3.0.1

  • 檢視指令參數

這裡為了友善查閱, 貼出完整的幫助文檔。

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

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

podman --help

manage pods and images

Usage:

podman [flags]

podman [command]

Available Commands:

attach Attach to a running container

build Build an image using instructions from Containerfiles

commit Create new image based on the changed container

container Manage Containers

cp Copy files/folders between a container and the local filesystem

create Create but do not start a container

diff Inspect changes on container's file systems

events Show podman events

exec Run a process in a running container

export Export container's filesystem contents as a tar archive

generate Generated structured data

healthcheck Manage Healthcheck

help Help about any command

history Show history of a specified image

image Manage images

images List images in local storage

import Import a tarball to create a filesystem image

info Display podman system information

init Initialize one or more containers

inspect Display the configuration of a container or image

kill Kill one or more running containers with a specific signal

load Load an image from container archive

login Login to a container registry

logout Logout of a container registry

logs Fetch the logs of a container

mount Mount a working container's root filesystem

network Manage Networks

pause Pause all the processes in one or more containers

play Play a pod

pod Manage pods

port List port mappings or a specific mapping for the container

ps List containers

pull Pull an image from a registry

push Push an image to a specified destination

restart Restart one or more containers

rm Remove one or more containers

rmi Removes one or more images from local storage

run Run a command in a new container

save Save image to an archive

search Search registry for image

start Start one or more containers

stats Display a live stream of container resource usage statistics

stop Stop one or more containers

system Manage podman

tag Add an additional name to a local image

top Display the running processes of a container

umount Unmounts working container's root filesystem

unpause Unpause the processes in one or more containers

unshare Run a command in a modified user namespace

varlink Run varlink interface

version Display the Podman Version Information

volume Manage volumes

wait Block on one or more containers

Flags:

--cgroup-manager string Cgroup manager to use (cgroupfs or systemd) (default "systemd")

--cni-config-dir string Path of the configuration directory for CNI networks

--config string Path of a libpod config file detailing container server configuration options

--conmon string Path of the conmon binary

--cpu-profile string Path for the cpu profiling results

--events-backend string Events backend to use

--help Help for podman

--hooks-dir strings Set the OCI hooks directory path (may be set multiple times)

--log-level string Log messages above specified level: debug, info, warn, error, fatal or panic (default "error")

--namespace string Set the libpod namespace, used to create separate views of the containers and pods on the system

--network-cmd-path string Path to the command for configuring the network

--root string Path to the root directory in which data, including images, is stored

--runroot string Path to the 'run directory' where all state information is stored

--runtime string Path to the OCI-compatible binary used to run containers, default is /usr/bin/runc

--storage-driver string Select which storage driver is used to manage storage of images and containers (default is overlay)

--storage-opt stringArray Used to pass an option to the storage driver

--syslog Output logging information to syslog as well as the console

--tmpdir string Path to the tmp directory

--trace Enable opentracing output

-v, --version Version of podman

Use "podman [command] --help" for more information about a command.

Podman 在覆寫 Docker 指令的同時,增加了對 Pod 操作的支援。

3.3 主機上測試編譯并推送鏡像

在使用上可以直接将

docker

指令替換為

podman

即可。

  • 編譯鏡像

1

2

3

4

5

6

7

8

9

10

11

12

13

echo -e 'FROM busybox\nRUN echo "hello world"' | podman build -t docker.io/shaowenchen/myimage:latest -

STEP 1: FROM busybox

Getting image source signatures

Copying blob 5c4213be9af9 done

Copying config 491198851f done

Writing manifest to image destination

Storing signatures

STEP 2: RUN echo "hello world"

hello world

STEP 3: COMMIT

4c8794086d9de80f71d182457b6d2cb18b9d61975b98bcd4cb167bdcabae5b2c

4c8794086d9de80f71d182457b6d2cb18b9d61975b98bcd4cb167bdcabae5b2c

  • 檢視編譯的鏡像

1

2

podman images |grep shaowenchen

docker.io/shaowenchen/myimage latest 4c8794086d9d 4 minutes ago 1.46 MB

  • 登入 DockerHub

1

2

3

4

podman login docker.io -u shaowenchen

Password:

Login Succeeded!

  • 推送鏡像

1

2

3

4

5

6

7

8

9

podman push docker.io/shaowenchen/myimage:latest

Getting image source signatures

Copying blob 2893437c336c done

Copying blob 84009204da3f done

Copying config 4c8794086d done

Writing manifest to image destination

Storing signatures

4. Jenkns 中使用 Podman 建構鏡像

4.1 關鍵配置

  • 使用 hostPath 将

    /var/lib/containers

    挂載到主機上

也可以使用 PVC,但是 PVC 可能需要加參數,見下文。

否則會有如下報錯:

1 Error: 'overlay' is not supported over overlayfs, a mount_program is required: backing file system is unsupported for this graph driver
  • privileged 特權模式

否則會有如下報錯:

1 Error: kernel does not support overlay fs: 'overlay' is not supported over extfs at "/var/lib/containers/storage/overlay": backing file system is unsupported for this graph driver
  • Podman 參數

    --cgroup-manager=cgroupfs

在使用 PVC 作為存儲目錄時, 需要考慮這項配置。核心通過 Cgroup Driver 隔離一組資源, 可選的參數有 cgroupfs 和 systemd, 需要與叢集環境保持一緻, 因為他們共用一個核心。我的測試環境使用的是 cgroupfs 。

否則會有如下報錯:

1 systemd cgroup flag passed, but systemd support for managing cgroups is not available
  • Podman 參數

    --events-backend=file

這項配置通常不會 Block 執行流程,如果你想保持日志更加幹淨,可以添加。

否則會有如下報錯:

1 unable to write system event: "write unixgram @0011c->/run/systemd/journal/socket: sendmsg: no such file or directory

4.2 示例一: 在 Jenkinsfile 中顯式使用 yaml 模闆

這裡将容器

/var/lib/containers

挂載到主機

/var/lib/containers

目錄,也可以挂載到主機

/tmp

目錄,并沒有強制要求。主機目錄隻是提供一個存放資料的地方。

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

pipeline {

agent {

kubernetes {

yaml """

apiVersion: v1

kind: Pod

spec:

containers:

- name: centos

image: centos:7

command:

- cat

tty: true

securityContext:

privileged: true

volumeMounts:

- name: storage

mountPath: /var/lib/containers

volumes:

- name: storage

hostPath:

path: /var/lib/containers

"""

}}

stages {

stage('Hello') {

steps {

container('centos') {

sh '''

curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable.repo https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/CentOS_7/devel:kubic:libcontainers:stable.repo

yum -y install podman

echo -e 'FROM busybox\nRUN echo "hello world"' | podman --events-backend=file build -t docker.io/shaowenchen1/myimage:latest -

podman --events-backend=file images |grep shaowenchen1

'''

}

}

}

}

}

Jenkins 的執行日志:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

···

Dependency Updated:

systemd.x86_64 0:219-78.el7_9.3 systemd-libs.x86_64 0:219-78.el7_9.3

Complete!

+ podman --events-backend=file build -t docker.io/shaowenchen1/myimage:latest -

+ echo -e 'FROM busybox

RUN echo "hello world"'

STEP 1: FROM busybox

STEP 2: RUN echo "hello world"

--> Using cache 4c8794086d9de80f71d182457b6d2cb18b9d61975b98bcd4cb167bdcabae5b2c

STEP 3: COMMIT docker.io/shaowenchen1/myimage:latest

--> 4c8794086d9

4c8794086d9de80f71d182457b6d2cb18b9d61975b98bcd4cb167bdcabae5b2c

+ podman --events-backend=file images

+ grep shaowenchen1

docker.io/shaowenchen1/myimage latest 4c8794086d9d 19 hours ago 1.46 MB

4.3 示例二: 使用 PVC 挂載

/var/lib/containers

目錄

在使用 PVC 存儲 Podman 資料時,需要提前準備好叢集的存儲。

  • 檢視叢集是否有預設的 StorageClass

1

2

3

4

5

6

7

kubectl get sc

NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE

openebs-device openebs.io/local Delete WaitForFirstConsumer false 19d

openebs-hostpath (default) openebs.io/local Delete WaitForFirstConsumer false 19d

openebs-jiva-default openebs.io/provisioner-iscsi Delete Immediate false 19d

openebs-snapshot-promoter volumesnapshot.external-storage.k8s.io/snapshot-promoter Delete Immediate false 19d

  • 為 Podman 建立一個 PVC

這裡的 namespace 需要與 Jenkins 中動态 Agent 所在的 namespace 保持一緻。

1

2

3

4

5

6

7

8

9

10

11

12

13

cat <<EOF | kubectl apply -f -

apiVersion: v1

kind: PersistentVolumeClaim

metadata:

name: storage

namespace: default

spec:

accessModes:

- ReadWriteOnce

resources:

requests:

storage: 30Gi

EOF

  • 檢視建立的 PVC

1

2

3

4

kubectl -n default get pvc

NAME STATUS VOLUME CAPACITY ACCESS MODES

storage Pending openebs-hostpath 11s

由于使用的是 WaitForFirstConsumer 模式,需要等到有 Pod 使用 PVC 時,才會綁定 PV。

  • 建立 Jenkins 流水線執行

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

pipeline {

agent {

kubernetes {

yaml """

apiVersion: v1

kind: Pod

spec:

containers:

- name: centos

image: centos:7

command:

- cat

tty: true

securityContext:

privileged: true

volumeMounts:

- name: storage

mountPath: /var/lib/containers

volumes:

- name: storage

persistentVolumeClaim:

claimName: storage

"""

}}

stages {

stage('Hello') {

steps {

container('centos') {

sh '''

curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable.repo https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/CentOS_7/devel:kubic:libcontainers:stable.repo

yum -y install podman

echo -e 'FROM busybox\nRUN echo "hello world"' | podman --events-backend=file build -t docker.io/shaowenchen2/myimage:latest -

podman --events-backend=file images |grep shaowenchen2

'''

}

}

}

}

}

Jenkins 的執行日志:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

···

Dependency Updated:

systemd.x86_64 0:219-78.el7_9.3 systemd-libs.x86_64 0:219-78.el7_9.3

Complete!

+ echo -e 'FROM busybox

RUN echo "hello world"'

+ podman --events-backend=file build -t docker.io/shaowenchen2/myimage:latest -

STEP 1: FROM busybox

STEP 2: RUN echo "hello world"

--> Using cache f4676f5b5e47a78970f2d97f4a5b77423f381e9742faae06d8c1a2d93bdb27c2

STEP 3: COMMIT docker.io/shaowenchen2/myimage:latest

--> f4676f5b5e4

f4676f5b5e47a78970f2d97f4a5b77423f381e9742faae06d8c1a2d93bdb27c2

+ podman --events-backend=file images

+ grep shaowenchen2

docker.io/shaowenchen2/myimage latest f4676f5b5e47 2 hours ago 1.46 MB

5. 總結

本文主要提供了一種在非 Docker 驅動的 Kubernetes 叢集中,進行 CICD 鏡像建構的思路,使用 Podman 替換 Docker 。選擇 Podman 的原因是, 其使用方式更貼近 Docker,而 Buildah 需要使用者修改鏡像編譯指令,因為 Buildah 使用的是

buildah bud

在生産實踐過程中,我們需要将 Podman 指令打包到 CI Agent 的基礎鏡像中。通過

alias docker=podman

, 對基于 Docker 指令的流水線進行替換。

下面簡單總結一下,使用 Podman 的要點:

  • 支援緩存。通過挂載

    /var/lib/containers

    目錄,可以緩存鏡像,并且可以根據業務劃分到不同目錄。
  • 與 Docker 無縫替換。如果有 hook 的地方,可以使用者無感覺地切換。
  • 更加通用。針對 OCI 标準實作,不依賴具體元件。
  • 特權模式。容器中運作 Podman 需要特權模式。容器套娃很難擺脫的運作模式。

6. 參考

  • https://github.com/kubernetes-sigs/cri-tools
  • http://docs.podman.io/en/latest/

原文連結 https://www.chenshaowen.com/blog/using-podman-to-build-images-under-kubernetes-and-jenkins.html

基于 Kubernetes 的 Jenkins 服務也可以去 Docker 了

更多精彩内容請關注公衆号