Podmanでgit,redmine,zabbix(+nginx reverse proxy)のコンテナを作ってみた | SEの道標
コンテナ

Podmanでgit,redmine,zabbix(+nginx reverse proxy)のコンテナを作ってみた

やったこと

podman で開発環境で便利そうな gitlab (コードバージョン管理), redmine (チケット管理), zabbix (サーバ監視) を立ててみた。

名付けて『開発環境の欲張りセット』。実用性は不明。

Docker ではなく Podman ?

RedHat さんが docker とは遊んじゃダメだって。。k8s に近しい podman と仲良くしなさいって言うんだ。。

だが世の中の情報は docker が多い感じ。特に docker-compose で構築する記事が多く、podman の例は少ない。Ubuntu ユーザーがうらやましい。

欲張りセットの設計図

家の PC の VirtualBOX にて RockyLinux 8.6 (minimal install) を立て、その上に下記環境を作った。仮想マシンのスペックは CPU: 4core, Mem: 16GB、ディスクは使用要件によってだが、とりあえず作るだけなら 16GB あれば OK。

青い●がコンテナ。工夫した点としては、、、

  • コンテナ間での通信があるものについては star-pod という pod を作ってまとめた。こうすると 127.0.0.1:[port] で通信ができる。ただし、ポートの競合を避けるため git はデフォルトの 80 から 3080 に Listen ポートを変更した。加えて git 内部で使われている puma が 8080 になっていて zabbix と競合するため、git の puma 側を 8079 に変更した。
  • git,redmine,zabbix のそれぞれに証明書を作るのはめんどいのでフロントの nginx で https 化し、そこから URL に応じてリバースプロキシを構成し http で流した (例えば https://git.example.com でアクセスしてきたら git コンテナへ http でリバプロ)。URL でアクセスできるよう bind で DNS サーバを立てた。
  • コンテナの mysql の仕様では複数 database での起動ができないので、/docker-entrypoint-initdb.d の初期化の機能を使ってみた。(mysql を 2 つたてて片方ポート変更でもよいのだが負荷が気になるので。)

といったところ。

事前準備

パッケージをインストールし firewalld に穴をあけ selinux は permissive にする。

$ sudo dnf -y module install container-tools
$ sudo dnf -y install podman-plugins git
$ sudo firewall-cmd --permanent --add-service={dns,http,https}
$ sudo firewall-cmd --permanent --add-port={10051/tcp,162/udp}
$ sudo firewall-cmd --reload; sudo firewall-cmd --list-all
$ sudo setenforce 0
$ sudo sed -ie 's/SELINUX=enforcing/SELINUX=permissive/g' /etc/selinux/config

また、rootless で tcp wellknown port (1024以下) を Listen するときは sysctl で net.ipv4.ip_unprivileged_port_start をデフォルトの 1024 から設定変更する。

$ sudo tee -a /etc/sysctl.conf <<EOF > /dev/null
net.ipv4.ip_unprivileged_port_start=53
EOF
$ sudo sysctl -p

手順 1 : bind (named) を立てる

hosts で変更してもよいがそれも面倒だしせっかくなので bind をコンテナで起動してみた。ただし、bind だけのイメージが無かったので rockylinux のイメージで作った。

コンテナは再起動するとディスク領域も初期化されるが、ディレクトリをマウントしておくと永続化される。なのでそのためのディレクトリ ~/srv/bind/ を作っておく。

$ mkdir -p ~/srv/bind/config/master

以下 3 つのファイルを新規作成する。1 はコンテナイメージ加工作業で使うため作業ディレクトリ、2 と 3 はコンテナ内で読み込むファイルのため永続化ディレクトリに作成する。

  1. Dockerfile
  2. named.conf  (bind の設定ファイル)
  3. example.com.zone (bind のゾーンファイル)
$ cat <<EOF > ~/srv/bind/Dockerfile
FROM docker.io/rockylinux/rockylinux

RUN dnf -y update
RUN dnf -y install bind procps iproute
RUN /usr/sbin/rndc-confgen -a -b 512 -k rndc-key
RUN chmod 755 /etc/rndc.key

EXPOSE 53/UDP
EXPOSE 53/TCP

CMD ["/usr/sbin/named", "-c", "/etc/bind/named.conf", "-g", "-u", "named"]
EOF
$ cat  <<EOF > ~/srv/bind/config/named.conf
include "/etc/rndc.key";
controls {
    inet 127.0.0.1 allow { 127.0.0.1; } keys { "rndc-key"; };
};

acl "myacl" {
    127.0.0.1;
    192.168.0.0/16;
};

options {
    directory "/etc/named/";
    pid-file "/run/named/";
    dump-file "/var/named/named_dump.db";
    statistics-file "/var/named/named.stats.log";

    zone-statistics yes;
    version "";
    masterfile-format text;
    recursive-clients 10000;
    tcp-clients 10000;

    allow-recursion { myacl; };
    allow-query { myacl; };
    allow-query-cache { myacl; };
};

view "internal" {
    recursion yes;

    zone "example.com" IN {
        type master;
        file "/etc/bind/master/example.com.zone";
    };
};
EOF
$ cat <<EOF > ~/srv/bind/config/master/example.com.zone
\$TTL 900
@     IN  SOA example.com. postmaster.example.com. (
        2020062101      ; Serial Number
        1800            ; Refresh
        900             ; Retry
        1209600         ; expire
        900             ; minimum
        )
;
      IN    NS  example.com.
      IN    A   192.168.211
www   IN    A   192.168.1.211
git    IN    A    192.168.1.211
redm    IN    A    192.168.1.211
zab    IN    A    192.168.1.211
EOF

192.168.1.211 はサーバ OS 本体の IP アドレス。資材がそろったところで docker イメージをダウンロードし加工しビルドしローカルイメージとして保存。

$ podman build -t bindrocky ~/srv/bind

そしてローカルイメージ bindrocky を起動 (run)。rootless で実行するために --net=slirp4netns:port_handler=slirp4netns の設定をする。-v で永続ディレクトリを指定。

$ podman run -d -t \
--net=slirp4netns:port_handler=slirp4netns \
-p 53:53 -p 53:53/udp \
--name bindrocky \
--restart always \
-v ~/srv/bind/config:/etc/bind:Z \
bindrocky

コマンドプロンプトで nslookup www.example.com 192.168.1.211 と打って 192.168.1.211 が返ってくれば OK。

手順 2 : star-pod を作り、その中に nginx を立てる

star-pod という名前の pod を作成。pod は kubernetes でも出てくる概念で、コンテナをひとまとめにする単位。前述の通り、コンテナ間通信が 127.0.0.1:[port] となるため、コンテナ間通信のあるコンテナを集めた。

$ podman pod create --name star-pod -p 80:80 -p 443:443 -p 10051:10051 -p 162:162/udp

外部から通信を受けるポートについては -p で指定する。今回はポート変換はしないため : の前後で同じ数字になっている。

そして先ほどと同様、永続化のディレクトリを作成。

$ mkdir -p ~/srv/fnginx/{config,public}

nginx の設定ファイルを取り出したいので、nginx コンテナをダウンロード&起動し、

$ podman run -d --name tmp-nginx docker.io/library/nginx

ファイルだけ取り出してコンテナを捨てる。(イメージは残る)

$ podman cp tmp-nginx:/etc/nginx/nginx.conf ~/srv/fnginx/config/
$ podman cp tmp-nginx:/etc/nginx/conf.d ~/srv/fnginx/config/
$ podman container rm -f tmp-nginx

この設定をする前に openssl で自己署名証明書を作ります。

$ openssl genrsa -out ~/srv/fnginx/config/conf.d/server.key 3072
$ openssl req -new -key ~/srv/fnginx/config/conf.d/server.key -out ~/srv/fnginx/config/conf.d/server.csr

以下 2 項目だけ設定し、他は空欄のまま Enter。

Country Name (2 letter code) [XX]:JP
Common Name (eg, your name or your server's hostname) []:www.example.com

サブジェクト代替名用のファイルを作成。

$ cat <<EOF > /srv/fnginx/config/conf.d/sans.txt
subjectAltName = DNS:www.example.com, DNS:zab.example.com, DNS:redm.example.com, DNS:git.example.com
EOF

このファイルを使って自己署名証明書を作成。

$ openssl x509 -req -days 365 -in ~/srv/fnginx/config/conf.d/server.csr -signkey ~/srv/fnginx/config/conf.d/server.key -out ~/srv/fnginx/config/conf.d/server.crt -extfile ~/srv/fnginx/config/conf.d/sans.txt

次に先ほど取り出した nginx の設定ファイルを編集。先頭から #access_log ~~ までのところを以下のように書き換える。

$ vi ~/srv/fnginx/config/conf.d/default.conf
server {
    listen       80;
    return 301 https://$host$request_uri;
}

server {
    listen       443 ssl http2 default_server;
    #listen  [::]:80;
    server_name  www.example.com;

    ssl_certificate "/etc/nginx/conf.d/server.crt";
    ssl_certificate_key "/etc/nginx/conf.d/server.key";
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA";
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout 10m;

    #access_log  /var/log/nginx/host.access.log  main;
~~~

include される conf ファイルも 3 つ新規作成。各サイト用。

$ cat <<EOF > /srv/fnginx/config/conf.d/git.conf
server {
    listen       443 ssl http2;
    server_name git.example.com;

    ssl_certificate "/etc/nginx/conf.d/server.crt";
    ssl_certificate_key "/etc/nginx/conf.d/server.key";
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA";
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout 10m;

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        proxy_pass http://127.0.0.1:3080;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header Host \$http_host;
        proxy_redirect off;
        proxy_set_header X-Forwarded-Proto \$scheme;
    }

    #error_page  404              /404.html;

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
EOF
$ cat <<EOF > /srv/fnginx/config/conf.d/redm.conf
server {
    listen       443 ssl http2;
    server_name redm.example.com;

    ssl_certificate "/etc/nginx/conf.d/server.crt";
    ssl_certificate_key "/etc/nginx/conf.d/server.key";
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA";
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout 10m;

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header Host \$http_host;
        proxy_redirect off;
        proxy_set_header X-Forwarded-Proto \$scheme;
    }

    #error_page  404              /404.html;

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
EOF
$ cat <<EOF > /srv/fnginx/config/conf.d/zab.conf
server {
    listen       443 ssl http2;
    server_name zab.example.com;

    ssl_certificate "/etc/nginx/conf.d/server.crt";
    ssl_certificate_key "/etc/nginx/conf.d/server.key";
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA";
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout 10m;

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header Host \$http_host;
        proxy_redirect off;
        proxy_set_header X-Forwarded-Proto \$scheme;
    }

    #error_page  404              /404.html;

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
EOF

動作確認用に index.html を作成。

$ cat <<EOF > /srv/fnginx/public/index.html
test
EOF

そして nginx コンテナを起動 (先ほど tmp でダウンロードしたイメージを使うため起動は早いはず)。

$ podman run -d -t \
--name fnginx \
--restart always --pod star-pod \
-v ~/srv/fnginx/config/nginx.conf:/etc/nginx/nginx.conf:Z \
-v ~/srv/fnginx/config/conf.d:/etc/nginx/conf.d:Z \
-v ~/srv/fnginx/public:/usr/share/nginx/html:Z \
nginx

ブラウザで http://[ip address] にアクセスすると警告画面にリダイレクトされ、進んだら test と表示されるはず。

手順 3 : gitlab を立てる

永続化するためのディレクトリを作成。

$ mkdir -p ~/srv/gitlab/{data,logs,config}

gitlab のコンテナを起動。gitlab はいくつかのミドルウェアが内包されていて、今回はそのうちの nginx と puma がポート競合するため変更する。

$ podman run -d -t \
--name gitlab \
--restart always --pod=star-pod \
-v ~/srv/gitlab/config:/etc/gitlab:Z \
-v ~/srv/gitlab/logs:/var/log/gitlab:Z \
-v ~/srv/gitlab/data:/var/opt/gitlab:Z \
--shm-size 256m \
-e GITLAB_OMNIBUS_CONFIG="nginx['listen_port']=3080;  puma['port']=8079;" \
docker.io/gitlab/gitlab-ee:latest

nginx は別途用意した Reverse Proxy 用の nginx と 80/tcp が競合するため 3080/tcp へ変更、puma は zabbix の WebUI と 8080/tcp が競合するため 8079/tcp へ変更。

1 GiB 以上のダウンロードが完了後、起動も3 分ほど。まずは WebUI の初期 ID である root のパスワードを表示。

$ podman exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password

表示されたパスワードを控え、https://git.example.com へアクセスします。警告画面の後に出てきたログイン画面で ID : root, PW : 先ほど控えたパスワードを入力し、ログインできることを確認します。

手順④: mysql を立てる

まずはいつも通り永続化するためのディレクトリ、、、を作成したいが、/var/lib/mysql をディレクトリでマウントすると原因不明の起動失敗に遭うのでボリューム volsqlvar でマウントする。また、/srv/mysql ディレクトリを /docker-entrypoint-initdb.d にマウントするため、このディレクトリも作成する。

$ mkdir -p ~/srv/mysql
$ podman volume create volsqlvar

そして /srv/mysql に以下の内容で reddb-create.sql を新規作成する。

$ cat <<EOF > /srv/mysql/reddb-create.sql
CREATE DATABASE IF NOT EXISTS redmine;
CREATE USER 'dbagent'@'%' IDENTIFIED BY 'P@ssw0rd';
GRANT ALL PRIVILEGES ON redmine.* TO 'dbagent'@'%' ;

そして mysql コンテナを起動。

$ podman run -d -t --name mysql \
--restart always --pod star-pod \
-e MYSQL_DATABASE="zabbix" \
-e MYSQL_USER="zabbix" \
-e MYSQL_PASSWORD="P@ssw0rd" \
-e MYSQL_ROOT_PASSWORD="P@ssw0rd" \
-v volsqlvar:/var/lib/mysql:Z \
-v ~/srv/mysql:/docker-entrypoint-initdb.d:Z \
docker.io/library/mysql \
--character-set-server=utf8 \
--collation-server=utf8_bin \
--default-authentication-plugin=mysql_native_password

正直、mysql を作るのが一番苦労した。原因未だ分からんがとにかく-v 周りがシビア。余計なカスタマイズするとハマるので、まずは上記からあまりカスタマイズしないことをお勧めする。

動作確認は以下の通りで、シェルに入れるか。一瞬入るけどすぐに出てしまったら、何かがおかしい状態。

$ podman exec -it mysql /bin/bash
bash-4.4# mysql -uroot -p
Enter password: P@ssw0rd
~~~
mysql> show databases;
mysql> quit;

最後の show databases; の結果で zabbix と redmine が両方表示されればとりあえず OK。

手順⑤: Redmine Web コンテナを起動する

永続化ディレクトリを作成。

$ mkdir -p ~/srv/redmine/files

コンテナ起動。

$ podman run -d -t \
--name redmine-web \
--restart always --pod star-pod \
-e REDMINE_DB_DATABASE="redmine" \
-e REDMINE_DB_USERNAME="dbagent" \
-e REDMINE_DB_PASSWORD="P@ssw0rd" \
-e REDMINE_DB_MYSQL="127.0.0.1" \
-v ~/srv/redmine/files:/usr/src/redmine/files:Z \
docker.io/library/redmine:latest

数分後に https://redm.example.com へアクセスし、右上の「ログイン」をクリックし、ログイン画面で ID/PW ともに admin でログインしてみる。

手順⑥: Zabbix Server と Zabbix Web のコンテナを起動する

DB (mysql) さえ永続していれば良いので永続化ディレクトリは不要。2 つのコンテナを起動する。まずは Zabbix Server。これが監視情報を収集する仕組みであり、WebUI は持たない。

$ podman run -d -t \
--name zab-sv \
--restart always --pod star-pod \
-e DB_SERVER_HOST="127.0.0.1" \
-e MYSQL_USER="zabbix" \
-e MYSQL_PASSWORD="P@ssw0rd" \
-e MYSQL_ROOT_PASSWORD="P@ssw0rd" \
docker.io/zabbix/zabbix-server-mysql:centos-trunk

んで WebUI を担当するのがこちらの Zabbix Web。

$ sudo podman run -d -t \
--name zab-web \
--restart always --pod star-pod \
-e DB_SERVER_HOST="127.0.0.1" \
-e MYSQL_USER="zabbix" \
-e MYSQL_PASSWORD="P@ssw0rd" \
-e ZBX_SERVER_HOST="127.0.0.1" \
-e PHP_TZ="Asia/Tokyo" \
docker.io/zabbix/zabbix-web-nginx-mysql:centos-trunk

https://zab.example.com へアクセスし、ID: Admin , PW: zabbix でログイン。

コンテナ, pod の自動起動

systemd を使ってコンテナを自動起動させる。

$ mkdir -p ~/.config/systemd/user && cd ~/.config/systemd/user
$ loginctl enable-linger <username>
$ podman generate systemd --name bindrocky -f
$ podman generate systemd --name star-pod -f
$ systemctl --user daemon-reload
$ systemctl --user enable bindrocky
$ systemctl --user enable pod-star-pod

「Failed to connect to bus: メディアが見つかりません」など言われたなら XDG_RUNTIME_DIR=/run/user/$UID systemctl --user を付ける。

以上!

コメント

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