こんにちは、テクマトリックスの酒井です。

弊社では CI 環境などの構築サービスを実施していますが、その中でコンテナを使って構築してほしいというご要望をいただくことが多くなりました。コンテナと言えば Docker がもっともよく知られているかと思いますが、 Linux をご利用のお客様からは Docker ではなく Podman を利用したいとのご要望も少なからずいただきます。

そこで今回は Fedora 37 に Podman をインストールして、コンテナの実行やコンテナイメージのビルドなどを行い、 Docker と同様に利用できるかを見ていきます。コンテナの題材として Jenkins の公式 Docker イメージ を使って説明します。

Podman とは

Podman のウェブサイト には次のように記載されています。

What is Podman? Podman is a daemonless container engine for developing, managing, and running OCI Containers on your Linux System. Containers can either be run as root or in rootless mode. Simply put: alias docker=podman.

https://podman.io/, (最終閲覧日:2023年2月24日)

これを日本語に翻訳すると次のようになります。

ポッドマンとは? Podman は、 Linux システムで OCI コンテナーを開発、管理、および実行するためのデーモンレス コンテナー エンジンです。コンテナーは、ルートとして実行することも、ルートレス モードで実行することもできます。簡単に言えば、 alias docker=podman です。

Google 翻訳を基に一部修正

alias docker=podman ということは、 docker コマンドと同じ使い方が podman コマンドでできると理解できます。実際のところどうなのかをこれから試していきます。

インストール

まずは Fedora 37 に Podman をインストールします。このあとの話を単純にするため、 Fedora の SELinux とファイアウォールは無効にしてあります。

Fedora では、標準のパッケージ管理ツール DNF を利用して Podman をインストールできます。次のコマンド一つを実行するだけでインストールできます。

sudo dnf install podman

インストール後にバージョンを確認すると、 4.4.1 がインストールされていました。

$ podman version
Client:       Podman Engine
Version:      4.4.1
API Version:  4.4.1
Go Version:   go1.19.5
Built:        Thu Feb  9 10:58:53 2023
OS/Arch:      linux/amd64

コンテナの実行

Jenkins の公式 Docker イメージの README に従って、 Jenkins のコンテナを実行してみます。実行するコマンドは次のとおりです。

podman run -p 8080:8080 -p 50000:50000 --restart=on-failure -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts

Jenkins の README の記載と異なるのは、先頭の dockerpodman に置き換えたことと、この後で利用するコンテナイメージ名を一致させるためにイメージ名のタグを lts-jdk11 から lts に変更しただけです。

コマンドを実行すると、イメージの選択が求められました。

$ podman run -p 8080:8080 -p 50000:50000 --restart=on-failure -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts
? Please select an image:
  ▸ registry.fedoraproject.org/jenkins/jenkins:lts
    registry.access.redhat.com/jenkins/jenkins:lts
    docker.io/jenkins/jenkins:lts
    quay.io/jenkins/jenkins:lts

Docker の場合はすぐに docker.io/jenkins/jenkins:lts というイメージのダウンロードが始まるのですが、Podmanではデフォルトのコンテナレジストリがないようです。入力待ちにならないよう、イメージ名はレジストリ名とともに指定するのが良さそうに思います。今回は docker.io/jenkins/jenkins:lts を選択して先に進みます。

$ podman run -p 8080:8080 -p 50000:50000 --restart=on-failure -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts
✔ docker.io/jenkins/jenkins:lts
Trying to pull docker.io/jenkins/jenkins:lts...
Getting image source signatures
Copying blob b51e09c8a0bd [==>-----------------------------------] 7.0MiB / 89.7MiB
(中略)
Writing manifest to image destination
Storing signatures
Running from: /usr/share/jenkins/jenkins.war
webroot: EnvVars.masterEnvVars.get("JENKINS_HOME")
(以下、 Jenkins のログが続く)

イメージがダウンロードされ、しばらくして Jenkins が起動しました。ブラウザで http://localhost:8080/ にアクセスすると、 Jenkins の「Unlock Jenkins」のページが表示されました。

では Ctrl-C を入力してコンテナを停止して、コンテナの一覧を確認します。

$ podman ps -a
CONTAINER ID  IMAGE                          COMMAND     CREATED        STATUS         PORTS                                             NAMES
adad0e39bf34  docker.io/jenkins/jenkins:lts              2 minutes ago  Up 44 seconds  0.0.0.0:8080->8080/tcp, 0.0.0.0:50000->50000/tcp  strange_ramanujan

コンテナを止めたはずなのにまだ実行されています。これは --restart=on-failure オプションの指定によりコンテナが再起動されたためです。このコンテナを停止して削除します。

$ podman stop strange_ramanujan
strange_ramanujan
$ podman rm strange_ramanujan
strange_ramanujan
$ podman ps -a
CONTAINER ID  IMAGE       COMMAND     CREATED     STATUS      PORTS       NAMES

コンテナを削除できました。ここまでは podman コマンドが docker コマンドの代わりとしてまったく同じに利用できました。

コンテナイメージのビルド

次に、 Jenkins の公式 Docker イメージをベースにしたコンテナイメージをビルドします。

先程と同じく、 Jenkins の公式 Docker イメージの README に従って2つのファイルを作成します。まずは executors.groovy を作ります。

import jenkins.model.*
Jenkins.instance.setNumExecutors(0) // Recommended to not run builds on the built-in node

次に Dockerfile を作ります。 Podman の作法としては Containerfile というファイル名の方が望ましいと思われますが、 Dockerfile でも動作します。

FROM jenkins/jenkins:lts
COPY --chown=jenkins:jenkins executors.groovy /usr/share/jenkins/ref/init.groovy.d/executors.groovy

必要なファイルを作ったので、コンテナイメージをビルドしてみます。

$ podman build -t myjenkins:lts-1 .
STEP 1/2: FROM jenkins/jenkins:lts
STEP 2/2: COPY --chown=jenkins:jenkins executors.groovy /usr/share/jenkins/ref/init.groovy.d/executors.groovy
COMMIT myjenkins:lts-1
--> d29a954eb4c
Successfully tagged localhost/myjenkins:lts-1
d29a954eb4cec58ca509b4ebe0e14d03b8273907043a9e143d65397cfd319f72

イメージをビルドできました。 Docker と違って、 -t オプションで指定したイメージ名にレジストリ名が含まれない場合、作成されるイメージ名の先頭に localhost/ が追加されます。イメージの一覧でも確認してみます。

$ podman image ls
REPOSITORY                 TAG         IMAGE ID      CREATED         SIZE
localhost/myjenkins        lts-1       d29a954eb4ce  52 seconds ago  476 MB
docker.io/jenkins/jenkins  lts         f16216f97fcb  2 weeks ago     476 MB

続いて、今ビルドしたイメージを使ってコンテナを実行します。

$ podman run -p 8080:8080 -p 50000:50000 --restart=on-failure -v jenkins_home:/var/jenkins_home localhost/myjenkins:lts-1
Running from: /usr/share/jenkins/jenkins.war
webroot: EnvVars.masterEnvVars.get("JENKINS_HOME")
(以下、 Jenkins のログが続く)

無事起動できました。 Ctrl-C でコンテナ止め、コンテナを確認します。

$ podman ps
CONTAINER ID  IMAGE                      COMMAND     CREATED             STATUS         PORTS                                             NAMES
b880ee90d824  localhost/myjenkins:lts-1              About a minute ago  Up 23 seconds  0.0.0.0:8080->8080/tcp, 0.0.0.0:50000->50000/tcp  hopeful_cerf

コンテナが再起動していることが確認できます。コンテナのログも見てみます。

$ podman logs hopeful_cerf
Running from: /usr/share/jenkins/jenkins.war
webroot: EnvVars.masterEnvVars.get("JENKINS_HOME")
(以下、 Jenkins のログが続く)

Jenkins のログが表示されました。最後にコンテナを停止し、コンテナとボリュームを削除します。

$ podman stop strange_ramanujan
hopeful_cerf
$ podman rm hopeful_cerf
hopeful_cerf
$ podman volume rm jenkins_home
jenkins_home

コンテナとボリュームを削除できました。ここまででも docker コマンドと同じく podman コマンドを利用できました。

Docker Compose はない、でもポッドがある

Jenkins の公式 Docker イメージの README には Docker Compose を使ってJenkins を実行する方法も記載されています。その方法とは、次のような内容の docker-compose.yml ファイルを作成して、 docker compose up -d コマンドを実行するというものです。

services:
  jenkins:
    image: jenkins/jenkins:lts
    ports:
      - "8080:8080"
    volumes:
      - jenkins_home:/var/jenkins_home
  ssh-agent:
    image: jenkins/ssh-agent
volumes:
  jenkins_home:

長いコマンドを打つ代わりに YAML ファイルでコンテナを定義できるのは魅力的です。しかし、 Podman には Docker Compose に対応する機能はありません。では別のツール、例えば Podman Compose を利用しなければならないのでしょうか。

いいえ、別の方法で実現できます。

Podman にはポッド(Pod)という形で複数のコンテナをまとめて管理する機能があります。この機能を利用すると、 Kubernetes の Pod などの設定ファイルに記述されたポッド・コンテナを実行することができます。

早速ポッドを試してみます。まずは Jenkins の README に記載の docker-compose.yml の例とおおむね同じポッドの設定ファイルを作成します。今回は jenkins-pod.yml というファイルに以下の内容を記述しました。実際のところ、このファイルにはポッド以外の設定(永続ボリューム要求)が含まれていますが、ここでは「ポッドの設定ファイル」と呼ぶことにします。

apiVersion: v1
kind: Pod
metadata:
  name: jenkins-pod
spec:
  containers:
  - name: jenkins
    image: docker.io/jenkins/jenkins:lts
    ports:
    - hostPort: 8080
      containerPort: 8080
    volumeMounts:
    - name: jenkins_home-pvc
      mountPath: /var/jenkins_home
  - name: ssh-agent
    image: docker.io/jenkins/ssh-agent
  volumes:
  - name: jenkins_home-pvc
    persistentVolumeClaim:
      claimName: jenkins_home
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins_home
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

ではコンテナを実行します。ポッドの設定ファイルからコンテナを作成・実行するには、 podman kube play コマンドを使用します。

$ podman kube play jenkins-pod.yml
Trying to pull docker.io/jenkins/ssh-agent:latest...
Getting image source signatures
(中略)
Writing manifest to image destination
Storing signatures
Volumes:
jenkins_home
Pod:
8116d152ca06993a764aec6fc1b55f8cea79b9bb9c33d7742f23cf038972f11f
Containers:
d25ac4c0ff31038661d4c2dbcbb24c199d3f7d3642371f33fd731b9fee4adde6
90d2313bb0529eb2d980e9f8a967ca6acb06ebf06ec2d03621f059f965e411aa

ボリューム、ポッド、そして2つのコンテナが作成され、コンテナが起動しました。コンテナを単独で起動したときと同様、ウェブブラウザで http://localhost:8080/ にアクセスすると「Unlock Jenkins」画面が表示されました。

実行中のポッドを確認するには、 podman pod ls コマンドを実行します。

$ podman pod ls
POD ID        NAME         STATUS      CREATED        INFRA ID      # OF CONTAINERS
8116d152ca06  jenkins-pod  Running     3 minutes ago  1a7c48ca265c  3

コンテナの一覧を見ると、ポッドの設定ファイルで定義した2つのコンテナ(とポッド管理用のコンテナ)が実行中であることが分かります。

$ podman ps
CONTAINER ID  IMAGE                                    COMMAND     CREATED             STATUS             PORTS                   NAMES
1a7c48ca265c  localhost/podman-pause:4.4.1-1675940333              3 minutes ago       Up About a minute  0.0.0.0:8080->8080/tcp  8116d152ca06-infra
d25ac4c0ff31  docker.io/jenkins/jenkins:lts                        3 minutes ago       Up About a minute  0.0.0.0:8080->8080/tcp  jenkins-pod-jenkins
90d2313bb052  docker.io/jenkins/ssh-agent:latest                   About a minute ago  Up About a minute  0.0.0.0:8080->8080/tcp  jenkins-pod-ssh-agent

このように、Docker Compose とは異なるものの、ポッドの設定ファイルから複数のコンテナを実行することができました。

ちなみに、 podman コマンドを使ってポッドを作成し、そこから設定ファイルを生成することができます。次のようにコマンドを実行すると、前述の jenkins-pod.yml に近い YAML ファイルが得られます。

podman pod create -p 8080:8080 jenkins-pod
podman container run --pod=jenkins-pod -d -v jenkins_home:/var/jenkins_home docker.io/jenkins/jenkins:lts
podman container run --pod=jenkins-pod -d docker.io/jenkins/ssh-agent
podman generate kube jenkins-pod > jenkins-pod.yml

クリーンアップ

最後に、これまで実行したコンテナやダウンロードしたイメージをすべて削除します。

まずはすべてのポッドを停止します。次のコマンドを実行します。

$ podman pod stop jenkins-pod
8116d152ca06993a764aec6fc1b55f8cea79b9bb9c33d7742f23cf038972f11f

ポッドに含まれるコンテナも停止します。当然ながら、 podman pod stop に対応する Docker のコマンドはありません。

そして、コンテナに関するあらゆるものを削除します。

$ podman system prune -a --volumes
WARNING! This command removes:
        - all stopped containers
        - all networks not used by at least one container
        - all volumes not used by at least one container
        - all images without at least one container associated with them
        - all build cache

Are you sure you want to continue? [y/N] y
ERRO[0004] Checking if infra needs to be stopped: stopping some containers: some containers failed
ERRO[0004] Checking if infra needs to be stopped: stopping some containers: some containers failed
ERRO[0004] Checking if infra needs to be stopped: pod has no infra container: no such container
Deleted Pods
8116d152ca06993a764aec6fc1b55f8cea79b9bb9c33d7742f23cf038972f11f
Deleted Volumes
jenkins_home
Deleted Images
f16216f97fcb99c68e03372f08859d8efd86e075e8bc22a0707d991b059a624a
504048d783865519a652773d9a5191eb0deab3314dab0c53c90f3fe8538bf288
c724b7bb09ece1d3004c08a8682de40be725b9b8694ccd47be762369c06c3f83
25a1e72a11d662de43279d3a701c0e8fad9daed0e3d28d2c78c566cba645143f
Deleted Networks
podman-default-kube-network
Total reclaimed space: 1.411GB

エラーが出力されたものの、コンテナ・ポッド・ネットワーク・ボリューム・イメージ・ビルドキャッシュすべてが削除されました。このコマンドは docker コマンドと同じです。

まとめ

Podman を利用してコンテナを実行しました。基本的に podman コマンドは docker コマンドと同じに利用できましたが、イメージ名は常にレジストリ名とともに指定するのが良さそうです。また docker-compose.yml の代わりにポッドの設定ファイルからコンテナを実行することができました。

また Podman を試した中で他にもいくつか Docker との違いに気づきました。これらについては次回の記事で紹介できればと思います。

参考

By tsakai

Jenkins関連のサービスやCloudBees製品を主に担当しています。 Certified CloudBees Jenkins Engineer (CCJE) および CloudBees CI DevOps Associate です。