podman環境でGitlab-runnerでCI/CDする

IT系

そもそもgitlab-runnerは何をするものなのか?アプリケーションの開発では、作ってテストしてマージしてという工程とテストが通った成果物をステージングや本番へリリースするという工程を頻繁に繰り返し実行することになります。これを継続的に実施するには自動化は欠かせません。その継続的な作業を自動化を実現するのが、gitlab-runnerとなります。Git/Gitlabを用いて複数人で開発を行う上で欠かせないツールとなるはずです。

gitlab-runnerの動作は昨今ではコンテナを用いるのが主流かと思います。このgitlab-runnerでテストをする環境をビルド(WEBフレームワークなどのバージョンや依存関係をリリースする状態に整えた環境)するのも完全仮想化の時代に比べてあっという間に終わり、テストにかかる時間が大幅に短縮されました。コンテナは今やもうなくてはならない技術と言えます。

ここでgitlab-runnerでもコンテナ技術を用いて開発をするわけですが、podman環境ではひと工夫する必要があります。podmanはdocker互換で同じ命令(API)で操作が可能なのでexecutorにdockerを指定して、podmanへunix socketを用いて命令を送ることで動作させることができます。

このunix socketはおそらく標準では作成されていないし、作成には管理者権限が必要となるので、条件的に厳しい場合はexecutorをshellにすることで力技で実行できると思われます(未実施)

gitlab-runnerのコンテナを起動する

APIサービスの起動

podmanはデフォルトデーモンレスですが、gitlab runnerから制御するためにdocker互換のunix socketを公開する必要がある

systemctl --user --now enable podman.socket   # root lessで実施する場合
systemctl --now enable podman.socket          # root環境で実施する場合

export DOCKER_HOST="unix://$(podman info -f '{{.Host.RemoteSocket.Path}}')"
echo $DOCKER_HOST

systemctl status --user podman.socket
sudo loginctl enable-linger gitlab-runner    # root less環境でログアウトしたら止まるときに実施

gitlab-runnerの起動

gitlabと同じpodで起動するようにします。あと、docker-composeをpodman環境でも使えるようにするためにpodman-composeを導入します。

python3 -m venv ./venv
source ./venv/bin/activate
pip install podman-compose

設定などを永続化するために以下の通りディレクトリを作成する

mkdir config config-runner data logs nginx

gitlabとgitlab-runnerのdocker-compose.ymlを作成しpodman-composeで起動します。また、今回試した環境ではIPアドレスを固定で設定できホスト名をdynaと設定した想定です。またgitlabのパスを変更し http://dyna/gitlab/ としてアクセスするようにしています。

services:
  gitlab:
    image: gitlab/gitlab-ce:latest
    restart: unless-stopped
    ports:
      - 8081:80
      - 2222:22
      - 5050:5050
    volumes:
      - './config:/etc/gitlab'
      - './logs:/var/log/gitlab'
      - './data:/var/opt/gitlab'
    shm_size: '256m'

  gitlab-runner:
    image: gitlab/gitlab-runner:alpine
    volumes:
      - './config-runner:/etc/gitlab-runner'
      - '/var/run/docker.sock:/var/run/docker.sock'
    restart: unless-stopped
  
  proxy:
    image: nginx:latest
    container_name: proxy
    ports:
      - '80:80'
    volumes:
      - './nginx:/etc/nginx'

./config/gitlab.rbに以下を追加

external_url 'http://dyna/gitlab'

NGINXを使ってホストの80番ポートをgitlabの8081へproxyさせる。これで http://dyna/gitlab としてgitlabにアクセスさせる。

./nginx/nginx.conf

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
  worker_connections 1024;
}

http {
  server {
    listen 80;
    server_name dyna 192.168.1.250;
    location /gitlab {
      proxy_pass http://dyna:8081/gitlab;
      proxy_set_header X-Forwarded-For $remote_addr;
    }
  }
}

/etc/hostsの設定追加

192.168.1.250 dyna

この設定はウェブブラウザやgitコマンドなどでgitlabにアクセスするホストに登録する必要があります。私はgitlabと同じホスト上でブラウザやgitコマンドでアクセスするため、gitlabのコンテナが動いている端末の/etc/hostsへの追加のみです。ただアプリによってはDNSしか使わないものがありますのでその際は内部にDNSを立てたりと少し面倒が発生しますがここでは説明しません。あと、/etc/nsswitch.confのhosts:の順が、files dns となっている必要がありますのでご注意。

プロジェクトへCI/CDを追加する

プロジェクトは作成されている状態を前提として進めさせて頂きます。

ここではプロジェクト名を「cicdtest」として進めます。プロジェクトを開いたら、左ペインの設定からCI/CDを選択します。

右ペインがCI/CDの設定の画面に変わりますので少し下にスクロールして「>Runner」を開いて、右上の「プロジェクトRunnerを作成」を選択する。

プロジェクトRunnerを作成ではタグ名のみ設定すればOKです。タグ名を入力したら「Runnerを作成」ボタンを押してください。

「Runnerを作成」を押下した後、Runnerの登録画面になります。今回の環境はPodmanが動作するOS(多分RHEL)としてLinuxを選択します。ステップ1、ステップ2をgitlab-runner上で実行します。ステップ1のtokenの値は後ほど使うのでメモしておいてください。

このまま一旦gitlab-runnerのコンテナの設定に移ります。

gitlab-runnerへ作成したRunnerを登録

起動したgitlab-runnerのコンテナにログインして先程作成したタグ名のRunnerを取得したTOKENを用いて登録する。ステップ1として出てきたコマンドに加えてexecutorやunix socketの場所の指定などを加えています。YOUR_TOKENはステップ1の例にあった値に置き換えてください。

podman-compose exec -it gitlab-runner /bin/bash

gitlab-runner register \
  --non-interactive \
  --url "http://dyna/gitlab/" \
  --registration-token "YOUR_TOKEN" \
  --executor "docker" \
  --docker-image "docker.io/alpine:latest" \
  --docker-host "unix:///var/run/podman/podman.sock" \
  --description "shared"

パイプラインの登録

ビルドやテストといったジョブを順に処理をさせることをパイプラインといい、これらの設定を .gitlab-ci.yml というファイルで定義します。ピリオドから始まるファイルですのでご注意下さい。

.gitlab-ci.yml

stages:
    - build
    - test
default:
  image: alpine
build:
    stage: build
    script:
        - echo "build OK"
test:
    stage: test
    script:
        - echo "test OK"
variables:
    # Podmanを使用する場合、TLS接続をオフにすることが一般的です
    DOCKER_TLS_CERTDIR: ""

作成が完了して更新を反映したらすぐにパイプライン処理が実行されます。さて、実行結果は・・・

gitlab-runnerのhostsで名前解決ができるようにすれば解決する。この名前解決は私の環境であるので皆さんは各々の環境に合わせて下さい。

vi config-runner/cofig.toml
---
<snip>
  [runners.docker]
    extra_hosts = ["dyna:192.168.1.250"]
---

podman-compose restart gitlab-runner

#11から#14の間に色々試しました….gitlab-ci.ymlの各ジョブのscriptでhostsに追加はNGでした。defaults: にextra_hosts を追加するのは管理上煩雑になる恐れがある。結局 gitlab-runnerのconfig.tomlのrunner.dockerにextra_hostsを追加することで動作するようにしました。

以上でひとまず、gitlab-runnerをpodmanで動作させることができるようになりました。あとは、buildやtestを組み立てて行きCI/CDを実現していきたいと思います。.gitlab-ci.ymlの書き方について機会があればまたメモしていこうと思います。

その他注意事項

注意:PodmanをDockerとして見せかける設定

RHEL8では、podman-docker パッケージをインストールすると、/var/run/docker.sock から /var/run/podman/podman.sock へのシンボリックリンクが自動的に作成される場合があります。

もしこのパッケージが入っている、あるいは手動でリンクを作成している場合は、--docker-host の指定を省略(またはデフォルトの unix:///var/run/docker.sock)にしても動作します。

# シンボリックリンクを手動で作成する場合(オプション)
ln -s /var/run/podman/podman.sock /var/run/docker.sock

どちらを選ぶべきか?

  • システム全体で1つのRunnerを共有し、リソース制限を気にしない場合ルート起動が設定も簡単で確実です。
  • セキュリティ要件が厳しく、特定のユーザー権限内にコンテナを閉じ込めたい場合 → 以前説明した Rootレス起動 を選択してください。

Podman のデーモンレスとunix socket利用のリスクとその運用

Podman は通常、

  • podman run ...
  • podman build ...

を実行した瞬間だけプロセスが起動し、常駐 daemon を持ちません。

一方、podman.socket を systemd で有効化すると、

  • Unix Socket 経由で API を受け付ける
  • 接続時に podman system service が起動する

という「socket activation」型になります。

つまり:

  • Docker のように常時 daemon が動き続けるわけではない
  • ただし API endpoint は待ち受ける

という中間的な状態です。


何が危険になるのか

問題は「daemon があるか」よりも、

socket にアクセスできる主体が、コンテナ作成権限を持つ

ことです。

例えば:

  • /run/podman/podman.sock
  • $XDG_RUNTIME_DIR/podman/podman.sock

にアクセスできると、

  • コンテナ起動
  • volume mount
  • image build
  • privileged container 起動

などが可能になります。

特に rootful Podman の socket は強力です。

これは Docker socket (/var/run/docker.sock) 問題とかなり似ています。


GitLab Runner でよくある構成

GitLab Runner で Podman を使う場合、よくあるのは:

  • shell executor + podman CLI
  • docker executor + podman socket
  • rootless podman socket

です。

この中で比較的安全なのは:

  • rootless user の Podman socket
  • CI専用ユーザー

です。


運用環境ではどうするべきか

あなたの感覚はかなり正しいです。

実運用では、

  • 「常時 socket 開放」
  • 「誰でもアクセス可能」

は避けるべきです。

おすすめは:

開発環境
  • podman socket 有効
  • CI/CD テスト用途
  • rootless user
  • localhost/unix socket 限定
本番環境

可能なら:

  • socket 無効
  • runner は shell executor で直接 podman 実行
  • あるいは build 専用ノードを分離

にするのが堅実です。


現実的な落とし所

かなり実務的には、以下がバランス良いです。

  • rootless Podman を使う
  • CI専用ユーザーを分離
  • socket 権限を最小化
  • 本番サーバでは socket 常時有効化しない
  • build 専用ホストを分離

例えば:

systemctl --user enable --now podman.socket

を CI ユーザーだけに限定する構成です。


さらに重要な点

もし runner 自体が侵害されると、

  • container escape
  • host filesystem mount
  • secret access

に繋がる可能性があります。

そのため、

  • runner を本番サーバに置かない
  • build node を分離する

のが本来は理想です。


結論としては:

  • podman.sock を有効化すると attack surface は増える
  • ただし Docker daemon 常駐とは少し違う
  • rootless + CI専用ユーザーならかなりマシ
  • 本番では常時有効化しない設計は合理的
  • CI/build 専用ホスト分離が最も安全

です。

タイトルとURLをコピーしました