【Kubernetes】k8s上のPodmanコンテナでDockerイメージをビルドする
現在、PJでk3上にJenkinsのCICDパイプラインを構築しています。
その中でDockerイメージのビルドはkanikoで行っているのですが、メンテに不安がある為、Podmanに置き換える事になりました。
kanikoについてはこちらの記事で紹介しています。
Podmanとは
Red Hatのエンジニアがオープンソース・コミュニティと共に開発した、コンテナを管理、実行する為のOSSツールです。
デーモンレスである点とroot権限が無くても動く事が大きな特徴だと思います。
参考
以下の記事を参考にしました。
- How to use Podman inside of a container | Enable Sysadmin
- How to use Podman inside of Kubernetes | Enable Sysadmin
- Best practices for running Buildah in a container | Red Hat Developer
- Build and run Buildah inside a Podman container | Red Hat Developer
- Podman in Docker を試してみた
構成
Podmanでレジストリにイメージをプッシュできる所まで確認したいので、Docker Desktopのk8上に以下のコンテナを起動して、動作確認したいと思います。
ディレクトリ構成
./ ├── conf │ └── registries.conf ├── input │ ├── Dockerfile │ └── nginx.conf ├── Dockerfile ├── coredns-cm.yml ├── htpasswd-secret.yml └── podman-k8s.yml
起動
conf/registries.conf
Podmanから参照するレジストリに関する設定ファイルです。
private.registry.local
は今回作成するDockerレジストリのホスト名です。
[registries.search] registries = ['docker.io', 'quay.io'] [registries.insecure] registries = ['private.registry.local']
Dockerfile
Podman実行用イメージのDockerfileです。
/var/lib/share
配下はうまく使われていないように思うのですが、今回は割愛😇
FROM alpine:latest ENV _CONTAINERS_USERNS_CONFIGURED "" RUN apk --no-cache add podman tzdata fuse-overlayfs slirp4netns curl RUN sed -i -e 's|^#mount_program|mount_program|g' -e 's|^# rootless_storage_path|rootless_storage_path|g' \ -e '/additionalimage.*/a "/var/lib/shared"' -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' /etc/containers/storage.conf \ && adduser --disabled-password podman \ && echo "podman:100000:65536" >> /etc/subuid \ && echo "podman:100000:65536" >> /etc/subgid \ && mkdir -p -m 755 /home/podman/.local/share/containers/storage \ /var/lib/shared/overlay-images /var/lib/shared/overlay-layers \ /var/lib/shared/vfs-images /var/lib/shared/vfs-layers \ && touch /var/lib/shared/overlay-images/images.lock \ && touch /var/lib/shared/overlay-layers/layers.lock \ && touch /var/lib/shared/vfs-images/images.lock \ && touch /var/lib/shared/vfs-layers/layers.lock \ && chown podman:podman -R /home/podman /var/lib/shared \ && mknod /dev/fuse -m 0666 c 10 229 COPY conf/registries.conf /etc/containers/registries.conf USER podman WORKDIR /home/podman CMD [ "sh", "-c", "while true; do sleep 1000; done" ]
ビルドしておきます。
$ docker build -t test-podman .
coredns-cm.yml
k8sの組込みDNSサーバーであるCoreDNS
の設定ファイルを修正して、クラスタ内でレジストリのホスト名が名前解決できるようにします。
$ alias k=kubectl # 設定ファイルがConfigMapとなっているので、取得して修正します $ k get cm -n kube-system coredns -o yaml > coredns-cm.yml # 以下の内容で修正 $ vi coredns-cm.yml apiVersion: v1 data: Corefile: | .:53 { errors health { lameduck 5s } ### ここから hosts { <自サーバーのIP> private.registry.local fallthrough } ### ここまでを追加 ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf { max_concurrent 1000 } cache 30 loop reload loadbalance } kind: ConfigMap metadata: name: coredns namespace: kube-system # 適用 $ k apply -f coredns-cm.yml
適用したら、DockerDesktopを再起動します。
CoreDNSポッド自体を再起動でもOK。
htpasswd-secret.yml
Dockerレジストリにログインする際のユーザー情報をSecretに登録します。
認証情報は以下のコマンドで生成しました。
# ユーザー名: podman-user、パスワード: fa0e473e6a60d3f7 $ docker run -it httpd:2.4.39-alpine htpasswd -nb -B podman-user fa0e473e6a60d3f7 > htpasswd # base64エンコードして出力 $ cat htpasswd | base64 xxxxx... # 出力結果をSecretに設定 $ cat htpasswd-secret.yml apiVersion: v1 kind: Secret metadata: name: htpasswd-secret data: htpasswd: <base64エンコードした認証情報> # 適用 $ k apply -f htpasswd-secret.yml
podman-k8s.yml
Ingressを使用するので、先にingress-nginx
コントローラーをインストールします。
$ k apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.4.0/deploy/static/provider/cloud/deploy.yaml
インストールしたら、以下のマニフェストを適用します。
$ cat podman-k8s.yml apiVersion: apps/v1 kind: Deployment metadata: name: podman labels: app: podman spec: replicas: 1 selector: matchLabels: app: podman template: metadata: labels: app: podman spec: containers: - name: podman image: test-podman imagePullPolicy: Never resources: limits: cpu: 200m memory: 500Mi env: - name: DOCKER_REGISTRY_USER value: "podman-user" - name: DOCKER_REGISTRY_PASSWD value: "fa0e473e6a60d3f7" # わかりやすくする為にそのまま記載しています。要Secret化 securityContext: privileged: true runAsUser: 1000 capabilities: drop: - NET_ADMIN - SYS_ADMIN - SYS_CHROOT - name: registry image: registry resources: limits: cpu: 100m memory: 100Mi ports: - containerPort: 5000 name: http protocol: TCP env: - name: REGISTRY_HTTP_SECRET value: a1f47b1c62945b5d - name: REGISTRY_AUTH value: htpasswd - name: REGISTRY_AUTH_HTPASSWD_REALM value: "Registry Realm" - name: REGISTRY_AUTH_HTPASSWD_PATH value: /auth/htpasswd - name: REGISTRY_HTTP_ADDR value: 0.0.0.0:5000 - name: REGISTRY_STORAGE_DELETE_ENABLED value: "true" volumeMounts: - mountPath: /auth name: htpasswd-secret-volume readOnly: true - name: registry-ui image: klausmeyer/docker-registry-browser:latest resources: limits: cpu: 100m memory: 200Mi ports: - containerPort: 8080 name: http protocol: TCP env: - name: DOCKER_REGISTRY_URL value: "http://private.registry.local" - name: ENABLE_DELETE_IMAGES value: "true" volumes: - name: htpasswd-secret-volume secret: secretName: htpasswd-secret items: - key: htpasswd path: htpasswd mode: 0400 --- apiVersion: v1 kind: Service metadata: name: registry-ui-svc labels: app: registry-ui-svc spec: type: ClusterIP selector: app: podman ports: - protocol: TCP port: 80 targetPort: 8080 name: registry-ui --- apiVersion: v1 kind: Service metadata: name: registry-svc labels: app: registry-svc spec: type: ClusterIP selector: app: podman ports: - protocol: TCP port: 80 targetPort: 5000 name: registry --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: podman-ingress annotations: nginx.ingress.kubernetes.io/proxy-body-size: 2G spec: ingressClassName: "nginx" rules: - host: private.registry.local http: paths: - path: / pathType: Prefix backend: service: name: registry-svc port: number: 80 - host: localhost http: paths: - path: / pathType: Prefix backend: service: name: registry-ui-svc port: number: 80 # 適用 $ k apply -f podman-k8s.yml deployment.apps/podman created service/registry-ui-svc created service/registry-svc created ingress.networking.k8s.io/podman-ingress created # 起動していることを確認 $ k get po,svc,ing NAME READY STATUS RESTARTS AGE pod/podman-6c6fd4c55f-sqh64 3/3 Running 0 22s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP xxx.xxx.xxx.xxx <none> 443/TCP 28h service/registry-svc ClusterIP xxx.xxx.xxx.xxx <none> 80/TCP 22s service/registry-ui-svc ClusterIP xxx.xxx.xxx.xxx <none> 80/TCP 22s NAME CLASS HOSTS ADDRESS PORTS AGE ingress.networking.k8s.io/podman-ingress nginx private.registry.local,localhost 80 22s
http://localhost
にアクセスすると、レジストリのWebUIが表示されます。
動作確認
PodmanのPODでイメージをビルドして、レジストリにpushまでしてみます。
$ k get po NAME READY STATUS RESTARTS AGE podman-6c6fd4c55f-sqh64 3/3 Running 0 5m21s $ k exec -it podman-6c6fd4c55f-sqh64 -c podman -- sh # ログイン ~ $ echo "${DOCKER_REGISTRY_PASSWD}" | podman login -u ${DOCKER_REGISTRY_USER} --password-stdin private.registry.local WARN[0000] "/" is not a shared mount, this could cause issues or missing mounts with rootless containers Login Succeeded! # ubuntuをpullして、レジストリ用のタグを付与します ~ $ podman pull ubuntu ✔ docker.io/library/ubuntu:latest Trying to pull docker.io/library/ubuntu:latest... Getting image source signatures Copying blob e96e057aae67 done Copying config a8780b506f done Writing manifest to image destination Storing signatures a8780b506fa4eeb1d0779a3c92c8d5d3e6a656c758135f62826768da458b5235 ~ $ podman tag docker.io/library/ubuntu private.registry.local/ubuntu ~ $ podman images REPOSITORY TAG IMAGE ID CREATED SIZE docker.io/library/ubuntu latest a8780b506fa4 3 days ago 80.3 MB private.registry.local/ubuntu latest a8780b506fa4 3 days ago 80.3 MB # レジストリにプッシュ ~ $ podman push private.registry.local/ubuntu Getting image source signatures Copying blob f4a670ac65b6 done Copying config a8780b506f done Writing manifest to image destination Storing signatures ~ $ exit
今度はDockerfileから作成したイメージを、レジストリにプッシュしてみます。
$ mkdir input $ vi input/Dockerfile # ネットからコピペしてきた、nginxを動かすだけのイメージです $ cat input/Dockerfile FROM centos:7 RUN yum update -y && yum clean all RUN yum install -y http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm RUN yum install -y --enablerepo=nginx nginx RUN yum swap -y fakesystemd systemd && yum clean all ADD nginx.conf /etc/nginx/conf.d/ RUN mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bk RUN systemctl enable nginx EXPOSE 80 ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;", "-c", "/etc/nginx/nginx.conf"] $ vi input/nginx.conf $ cat input/nginx.conf server { listen 80 default_server; root /var/www/html; index index.html index.htm; server_name nginx; location / { try_files $uri $uri/ =404; } }
Podmanコンテナに↑のファイルをcpします。
$ k cp input podman-6c6fd4c55f-sqh64:/home/podman -c podman $ k exec -it podman-6c6fd4c55f-sqh64 -c podman -- sh # コピーされたことを確認 ~ $ ls input/ Dockerfile nginx.conf # イメージをビルドする ~ $ podman build -t private.registry.local/test-nginx:0.0.1 input STEP 1/10: FROM centos:7 ✔ docker.io/library/centos:7 Trying to pull docker.io/library/centos:7... Getting image source signatures Copying blob 2d473b07cdd5 done Copying config eeb6ee3f44 done Writing manifest to image destination Storing signatures STEP 2/10: RUN yum update -y && yum clean all Loaded plugins: fastestmirror, ovl ..... STEP 10/10: ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;", "-c", "/etc/nginx/nginx.conf"] COMMIT private.registry.local/test-nginx:0.0.1 --> 615f0d391a9 Successfully tagged private.registry.local/test-nginx:0.0.1 615f0d391a99dfadb871b55ae23fb519e38a73c6ab53f2c8aa25f21497838926 # 作成したイメージをプッシュ ~ $ podman push private.registry.local/test-nginx:0.0.1 Getting image source signatures Copying blob d2af15141490 done Copying blob 174f56854903 done Copying blob caa980c80a8b done Copying blob 66ef84f422dc done Copying blob 26dcfab9f201 done Copying blob b357d4ab9171 done Copying blob faa8fdc2852c done Copying blob 9473b225969a done Copying config 615f0d391a done Writing manifest to image destination Storing signatures ~ $
ブラウザに戻ってリロードすると、プッシュしたイメージが表示されている事が確認できます。
注意
PodmanやBuildahでビルドしたイメージは、デフォルトでOCI Image Formatとなっています。
古めのレジストリにプッシュする場合、OCI Image Formatに対応していない可能性があります。
その場合、ビルド時に--format docker
を付与するとプッシュ可能となります。
また、プッシュ時に--format v2s2
を付与しても良いようです。