前提
- アクセスログを取得したいなどの理由で、docker compose環境でKeycloakの前段にリバースプロキシサーバーを立てたくなった
- 使用したKeycloakのDockerイメージ:
quay.io/keycloak/keycloak:26.0.6
まずはNginx
まずは使い慣れているNginxでやろうとした。Dockerイメージはnginx:1.27.3を使用。
compose.yml
services: proxy: image: nginx:1.27.3 ports: - "18080:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf keycloak: image: quay.io/keycloak/keycloak:26.0.6 command: - start-dev environment: KC_BOOTSTRAP_ADMIN_USERNAME: admin KC_BOOTSTRAP_ADMIN_PASSWORD: password KC_DB: postgres KC_DB_URL: jdbc:postgresql://keycloak_db:5432/keycloak KC_DB_USER: keycloak KC_DB_PASSWORD: password KC_HOSTNAME: http://localhost:18080 depends_on: keycloak_db: condition: service_healthy keycloak_db: image: postgres:16 environment: POSTGRES_DB: keycloak POSTGRES_USER: keycloak POSTGRES_PASSWORD: password healthcheck: test: ["CMD", "pg_isready", "-U", "admin"] timeout: 1s retries: 10
nginx.conf
worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; server { listen 80; server_name proxy; location / { proxy_pass http://keycloak:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } }
結果
- 前提:
keycloakはポスグレコンテナをwaitしてから起動する - → コンテナの起動順が
nginx > keycloakのため、host not found in upstreamが発生してnginxのコンテナが異常終了してしまう
$ docker compose up ...省略 proxy-1 | 2025/01/18 01:54:59 [emerg] 1#1: host not found in upstream "keycloak" in /etc/nginx/nginx.conf:16 proxy-1 | nginx: [emerg] host not found in upstream "keycloak" in /etc/nginx/nginx.conf:16 ...省略
keycloakコンテナのwaitを試みる
以下の公式ドキュメントとZennの記事を参考に、depends_onとhealthcheckを使って以下のようにnginxコンテナがkeycloakコンテナをwaitするように試みた。
services: proxy: # 省略 depends_on: # 追加 keycloak: condition: service_healthy # 省略 keycloak: # 省略 environment: # 省略 KC_HEALTH_ENABLED: true # 追加 # 省略 healthcheck: # 追加 test: ["CMD", "curl", "--head", "-fsS", "localhost:8080/health/ready"] interval: 1s retries: 10
結果は、以下のようにkeycloakコンテナがhealthyにならず、起動に失敗した。
dependency failed to start: container keycloak-proxy-poc-keycloak-1 is unhealthy
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e3c71923a3be quay.io/keycloak/keycloak:26.0.6 "/opt/keycloak/bin/k…" 3 minutes ago Up 3 minutes (unhealthy) 8080/tcp, 8443/tcp, 9000/tcp keycloak-proxy-poc-keycloak-1 7ca44f2f1438 postgres:16 "docker-entrypoint.s…" 3 minutes ago Up 3 minutes (healthy) 5432/tcp keycloak-proxy-poc-keycloak_db-1
docker inspectで調査すると、コンテナ内でcurlコマンドがnot foundになっていた。
OCI runtime exec failed: exec failed: unable to start container process: exec: \"curl\": executable file not found in $PATH: unknown
$ docker inspect e3c71923a3be
...省略
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 35784,
"ExitCode": 0,
"Error": "",
"StartedAt": "2025-01-18T02:24:47.83521138Z",
"FinishedAt": "0001-01-01T00:00:00Z",
"Health": {
"Status": "unhealthy",
"FailingStreak": 337,
"Log": [
{
"Start": "2025-01-18T11:30:36.773188347+09:00",
"End": "2025-01-18T11:30:36.821814797+09:00",
"ExitCode": -1,
"Output": "OCI runtime exec failed: exec failed: unable to start container process: exec: \"curl\": executable file not found in $PATH: unknown"
},
{
"Start": "2025-01-18T11:30:37.823004357+09:00",
"End": "2025-01-18T11:30:37.872562768+09:00",
"ExitCode": -1,
"Output": "OCI runtime exec failed: exec failed: unable to start container process: exec: \"curl\": executable file not found in $PATH: unknown"
},
{
"Start": "2025-01-18T11:30:38.873552379+09:00",
"End": "2025-01-18T11:30:38.921239493+09:00",
"ExitCode": -1,
"Output": "OCI runtime exec failed: exec failed: unable to start container process: exec: \"curl\": executable file not found in $PATH: unknown"
},
{
"Start": "2025-01-18T11:30:39.926112776+09:00",
"End": "2025-01-18T11:30:39.973088461+09:00",
"ExitCode": -1,
"Output": "OCI runtime exec failed: exec failed: unable to start container process: exec: \"curl\": executable file not found in $PATH: unknown"
},
{
"Start": "2025-01-18T11:30:40.974976076+09:00",
"End": "2025-01-18T11:30:41.01868977+09:00",
"ExitCode": -1,
"Output": "OCI runtime exec failed: exec failed: unable to start container process: exec: \"curl\": executable file not found in $PATH: unknown"
}
]
}
},
...省略
公式ドキュメントやissueの議論を読むと、以下のようなことらしい
- セキュリティを考慮してアタックサーフェスを減らすため、bashとKeycloakを実行するのに必要な最小限のパッケージしかインストールされていない
- microdnf, dnf, rpmといったパッケージ管理用のコマンドもインストールされていない
If you try to install new software in a stage FROM quay.io/keycloak/keycloak, you will notice that microdnf, dnf, and even rpm are not installed.
Nginx以外のリバースプロキシを試す
- keycloakのイメージをカスタマイズして
curlを使えるようにするのも大変そう - → やりたいことは何らかのリバースプロキシを立てることであって、Nginxである必要はない
- → リバースプロキシ先のホストが立ち上がってない場合にwaitしてくれるリバースプロキシ製品があるならそれを使えばいいのでは?
と考えて、ChatGPTに相談してみた。すると、以下のようなプロダクトを紹介してくれた。
- Traefik: https://github.com/traefik/traefik
- Envoy: https://www.envoyproxy.io/
- Caddy: https://caddyserver.com/
Envoyは名前だけ知ってたが、他の2つは初めて聞いた。
まずはChatGPT曰く設定がシンプルというCaddyを試してみる
Caddyで再チャレンジ
Caddyも公式のDockerイメージが存在する。
https://hub.docker.com/_/caddy
nginx部分を以下のように書き換える。
compose.yml
services: proxy: image: caddy:2.9.1 ports: - "18080:80" volumes: - ./Caddyfile:/etc/caddy/Caddyfile # ...省略
Caddyの場合は、Caddyfileというのが設定ファイルの名前らしい。
以下のように公式ドキュメントを参考にしつつみよう見真似で書いてみる。
デフォルトだと443ポートで起動する仕様だったので、80ポートで起動させたい場合は明示が必要だった。
Caddyfile
localhost:80 {
reverse_proxy keycloak:8080 {
health_uri /health/ready
health_interval 1s
health_timeout 3s
}
}
proxyコンテナのdepends_onとkeycloakコンテナのhealthcheckは不要なはずなので削除する。
- 起動すると、Caddyは何度もリトライしてくれていて、途中で
no such hostからconnection refusedに切り替わっている(=ホストは認識できている)。- → Nginxで詰まった課題は乗り越えられた。
- ただ、次に
http://keycloak:8080/health/readyが404になってしまっている。
{"level":"info","ts":1737170935.4018724,"logger":"http.handlers.reverse_proxy.health_checker.active","msg":"status code out of tolerances","status_code":404,"host":"keycloak:8080"}
$ docker compose up ...省略 proxy-1 | {"level":"info","ts":1737170894.3574111,"logger":"http.handlers.reverse_proxy.health_checker.active","msg":"HTTP request failed","host":"keycloak:8080","error":"Get \"http://keycloak:8080/health/ready\": dial tcp: lookup keycloak on 127.0.0.11:53: no such host"} ...省略 proxy-1 | {"level":"info","ts":1737170926.3481672,"logger":"http.handlers.reverse_proxy.health_checker.active","msg":"HTTP request failed","host":"keycloak:8080","error":"Get \"http://keycloak:8080/health/ready\": dial tcp 192.168.107.4:8080: connect: connection refused"} proxy-1 | ...省略{"level":"info","ts":1737170935.4018724,"logger":"http.handlers.reverse_proxy.health_checker.active","msg":"status code out of tolerances","status_code":404,"host":"keycloak:8080"} ...省略
Keycloakのバージョン26.0.0からヘルスチェックのエンドポイントはデフォルトで9000ポートに変わったらしい。
https://www.keycloak.org/docs/latest/upgrading/#management-port-for-metrics-and-health-endpoints
Caddyfileにhealth_port 9000を追加する。
localhost:80 {
reverse_proxy keycloak:8080 {
health_port 9000
# 省略
}
}
これでCaddyからヘルスチェック成功のログが出るようになった。
proxy-1 | {"level":"info","ts":1737174779.735086,"logger":"http.handlers.reverse_proxy.health_checker.active","msg":"host is up","host":"keycloak:9000"}
localhost:18080にアクセスしたら、Caddy越しにKeycloakのTOPが表示できた。

アクセスログの出力
この設定だけだと、アクセスログがコンソールに出なかったので出したい。
公式ドキュメントにlogというページがあるので、参照しながらCaddyfileに以下の設定を追加する。
localhost:80 {
reverse_proxy keycloak:8080 {
# ...省略
}
log { # 追加
output stdout
}
}
これでアクセスログは出るようになったが、ほしいのはKeycloak自体へのアクセスログのみなのに、JSやCSSのログも出力されてノイズになってしまった。
log_skipというディレクティブがあるので、これで対応することができた。
Keycloakの場合は、staticコンテンツは/resoources/配下に配置されているようなので、これをパスごと除外する。
localhost:80 {
reverse_proxy keycloak:8080 {
# 省略
}
log {
output stdout
}
log_skip /resources/*
}
上記の設定でstaticコンテンツのアクセスログが出力されなくなった。ただ、Keycloakの画面自体が1画面で複数のhtmlを取得する設計でできているみたいで、1回の画面へのアクセスでログが8行くらい出る。
目的は達成できたのでこの記事はここまでとする。
所感
- 今回は技術検証だったが、仕事の場合もアプリケーションサーバーの前段のプロキシサーバーは深く考えずにNginxを選定してしまうことが多かった。
- → 言われてみるとNginxを使い倒している訳ではない。
- → リバースプロキシが必要なだけなら、もっと適したプロダクトがあるのかもしれない。
- → 今回は初手のCaddyでうまくいってしまったので触る機会がなかったTraefikやEnvoyも触ってみたいなと思った