【Kubernetes】k8s上のPodmanコンテナでDockerイメージをビルドする

 現在、PJでk3上にJenkinsのCICDパイプラインを構築しています。
その中でDockerイメージのビルドはkanikoで行っているのですが、メンテに不安がある為、Podmanに置き換える事になりました。
 kanikoについてはこちらの記事で紹介しています。

Podmanとは

 Red Hatのエンジニアがオープンソース・コミュニティと共に開発した、コンテナを管理、実行する為のOSSツールです。
デーモンレスである点とroot権限が無くても動く事が大きな特徴だと思います。

www.redhat.com

github.com

参考

以下の記事を参考にしました。

構成

 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

github.com

インストールしたら、以下のマニフェストを適用します。

$ 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を付与しても良いようです。