やったこと
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 はコンテナ内で読み込むファイルのため永続化ディレクトリに作成する。
- Dockerfile
- named.conf (bind の設定ファイル)
- 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 を付ける。
以上!
コメント