1. 実稼働環境への導入チェックリスト
Keycloakを実稼働環境にデプロイするには、データベース、ネットワーキング、JVM、キャッシュ、セキュリティを慎重に構成する必要があります。この記事では、データベースの選択から負荷テストまでの包括的なガイダンスを提供します。
┌─────────────────────────────────────────────────────────────┐
│ Production Checklist │
├─────────────────────────────────────────────────────────────┤
│ ✅ Database: PostgreSQL + Connection Pool tuning │
│ ✅ Build: kc.sh build --optimized │
│ ✅ Hostname: hostname-v2 provider configured │
│ ✅ Proxy: X-Forwarded-* / PROXY protocol │
│ ✅ TLS: Certificates configured │
│ ✅ JVM: Heap, GC, container-aware settings │
│ ✅ Caching: Infinispan local caches tuned │
│ ✅ Metrics: /metrics endpoint enabled │
│ ✅ Health: /health/ready, /health/live enabled │
│ ✅ Load Test: Gatling benchmark passed │
└─────────────────────────────────────────────────────────────┘
2. データベースの選択と構成
2.1 データベースサポートマトリックス
| データベース | ベンダーフラグ | 推奨 | 注記 |
|---|---|---|---|
| PostgreSQL | ポストグレ | ✅ はい | Keycloakチームによって最もテストされた最高のパフォーマンス |
| MySQL | mysql | ⚠️ わかりました | InnoDB、utf8mb4 文字セットが必要 |
| マリアDB | マリアドブ | ⚠️ わかりました | MySQLに似ている |
| オラクル | オラクル | ⚠️ わかりました | エンタープライズライセンスが必要です |
| Microsoft SQLサーバー | mssql | ⚠️ わかりました | Windows環境 |
| H2 (埋め込み) | 開発ファイル/開発メモリ | ❌ いいえ | 開発用途のみ |
2.2 PostgreSQLの構成
# Cấu hình database cơ bản
bin/kc.sh start \
--db=postgres \
--db-url="jdbc:postgresql://db-host:5432/keycloak" \
--db-username=keycloak \
--db-password=secure_password_here \
--db-schema=public
環境変数を使用する場合 (コンテナーに推奨):
# Environment variables cho database
export KC_DB=postgres
export KC_DB_URL="jdbc:postgresql://db-host:5432/keycloak"
export KC_DB_USERNAME=keycloak
export KC_DB_PASSWORD=secure_password_here
export KC_DB_SCHEMA=public
# JDBC URL với advanced parameters
export KC_DB_URL="jdbc:postgresql://db-host:5432/keycloak?ssl=true&sslmode=verify-full&sslrootcert=/certs/ca.crt"
2.3 接続プールのチューニング (Agroal)
キークロークの使用アグロル接続プール (Quarkus のデフォルト)。接続プールのチューニングは、パフォーマンスに影響を与える重要な要素です。
# Connection pool configuration
bin/kc.sh start \
--db=postgres \
--db-url="jdbc:postgresql://db-host:5432/keycloak" \
--db-username=keycloak \
--db-password=secure_password_here \
--db-pool-initial-size=25 \
--db-pool-min-size=25 \
--db-pool-max-size=100
| パラメータ | デフォルト | プロダクション推奨 | 説明する |
|---|---|---|---|
--db-プールの初期サイズ | 0 | 25 | 最初に初期化された接続の数 |
--db-プールの最小サイズ | 0 | 25 | 維持される接続の最小数 |
--db-プールの最大サイズ | 100 | 50~100 | 最大接続数 |
接続プールのサイジングの原則:
Tổng connections = Số Keycloak instances × db-pool-max-size
Ví dụ: 3 instances × 100 max = 300 connections
→ PostgreSQL max_connections ≥ 300 + buffer (20%)
→ Đặt max_connections = 360
サーバー側の PostgreSQL 構成 (postgresql.conf):
# postgresql.conf - tối ưu cho Keycloak
max_connections = 400
shared_buffers = 2GB
effective_cache_size = 6GB
work_mem = 16MB
maintenance_work_mem = 512MB
# Connection timeout
idle_in_transaction_session_timeout = 30000 # 30 seconds
statement_timeout = 60000 # 60 seconds
# WAL configuration
wal_level = replica
max_wal_senders = 5
wal_keep_size = 1GB
3. ビルドの最適化
3.1 構築フェーズと開始フェーズ
Keycloak には 2 つのフェーズがあります。建てる(構成をカプセル化) および始める(サーバーを実行します)。本番環境では常に使用します--最適化された2 つのフェーズを分離し、スタートアップを大幅に高速化します。
┌──────────────────────────────────────────────────────────────┐
│ Keycloak Build & Start Phases │
├──────────────────────────────────────────────────────────────┤
│ │
│ Phase 1: BUILD (chạy 1 lần, hoặc khi config thay đổi) │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ kc.sh build │ │
│ │ - Parse configuration │ │
│ │ - Install providers/extensions │ │
│ │ - Quarkus augmentation (ahead-of-time optimization) │ │
│ │ - Persist build options │ │
│ └────────────────────────────────────────────────────────┘ │
│ ↓ │
│ Phase 2: START (mỗi lần khởi động) │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ kc.sh start --optimized │ │
│ │ - Skip build phase → Fast startup │ │
│ │ - Use pre-built configuration │ │
│ │ - Apply runtime-only options │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
# Build phase - chạy khi build Docker image
bin/kc.sh build \
--db=postgres \
--features=token-exchange,admin-fine-grained-authz \
--health-enabled=true \
--metrics-enabled=true \
--http-relative-path=/auth
# Start phase - chạy khi container start
bin/kc.sh start --optimized \
--db-url="jdbc:postgresql://db-host:5432/keycloak" \
--db-username=keycloak \
--db-password=secure_password_here \
--hostname=auth.example.com \
--https-certificate-file=/certs/tls.crt \
--https-certificate-key-file=/certs/tls.key
3.2 本番環境の Dockerfile
# Multi-stage Dockerfile cho Keycloak Production
FROM quay.io/keycloak/keycloak:26.0 AS builder
# Build phase - pre-build configuration
ENV KC_DB=postgres
ENV KC_HEALTH_ENABLED=true
ENV KC_METRICS_ENABLED=true
ENV KC_FEATURES=token-exchange,admin-fine-grained-authz
ENV KC_HTTP_RELATIVE_PATH=/auth
# Thêm custom providers nếu có
# COPY --chown=keycloak:keycloak my-provider.jar /opt/keycloak/providers/
# Thêm custom themes nếu có
# COPY --chown=keycloak:keycloak my-theme/ /opt/keycloak/themes/my-theme/
RUN /opt/keycloak/bin/kc.sh build
# Runtime stage
FROM quay.io/keycloak/keycloak:26.0
COPY --from=builder /opt/keycloak/ /opt/keycloak/
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
CMD ["start", "--optimized"]
4. ホスト名の設定
4.1 ホスト名 v2 プロバイダー
キークロークの使用ホスト名-v2プロバイダー (Keycloak 25 以降のデフォルト) を使用して、すべてのエンドポイント (フロントエンド、バックエンド、管理) の URL を定義します。
# Hostname configuration cơ bản
bin/kc.sh start --optimized \
--hostname=auth.example.com \
--hostname-admin=admin-auth.example.com
| パラメータ | 説明する | 例えば |
|---|---|---|
--ホスト名 | フロントエンド URL のホスト名 (ログイン ページ、既知のエンドポイント) | auth.example.com |
--ホスト名-管理者 | 管理コンソール用の別のホスト名 (フロントエンドと異なる場合)。設定なし = 共有--ホスト名 | admin-auth.internal.com |
--ホスト名-strict | 構成されたホスト名へのリクエストのみを許可します。デフォルト:真実 | 真実 |
--ホスト名-バックチャネル-動的 | バックエンド URL は、固定ホスト名の代わりに要求ホスト名を使用します。デフォルト:間違い | 間違い |
4.2 ホスト名のシナリオ
# Scenario 1: Single domain cho tất cả
bin/kc.sh start --optimized \
--hostname=auth.example.com
# Scenario 2: Tách Admin Console ra domain riêng (khuyến nghị production)
bin/kc.sh start --optimized \
--hostname=auth.example.com \
--hostname-admin=admin-auth.internal.example.com
# Scenario 3: Edge proxy determine hostname từ request
bin/kc.sh start --optimized \
--hostname-strict=false \
--proxy-headers=xforwarded
# Scenario 4: Backchannel dynamic (backend-to-backend dùng internal URL)
bin/kc.sh start --optimized \
--hostname=auth.example.com \
--hostname-backchannel-dynamic=true
5. プロキシ設定
5.1 プロキシヘッダー
Keycloakがリバースプロキシ(Nginx、HAProxy、AWS ALBなど)の背後にある場合、Keycloakが正しいクライアントIP、プロトコル、およびホスト名を受信できるようにプロキシヘッダーを設定する必要があります。
# Option 1: X-Forwarded-* headers (phổ biến nhất)
bin/kc.sh start --optimized \
--proxy-headers=xforwarded
# Option 2: RFC 7239 Forwarded header
bin/kc.sh start --optimized \
--proxy-headers=forwarded
| ヘッダ | 目的 | フラグ |
|---|---|---|
X-Forwarded-For | クライアントIPアドレス | xforwarded |
X-Forwarded-Proto | 独自プロトコル(http/https) | xforwarded |
X 転送ホスト | 元のホスト名 | xforwarded |
X 転送ポート | 元のポート | xforwarded |
転送されました | RFC 7239 結合ヘッダー | 転送されました |
5.2 Nginx リバースプロキシ構成
# /etc/nginx/conf.d/keycloak.conf
upstream keycloak_backend {
server keycloak-1:8443;
server keycloak-2:8443;
# Sticky session based on KEYCLOAK_SESSION cookie
sticky cookie KEYCLOAK_ROUTE expires=1h domain=.example.com httponly secure;
}
server {
listen 443 ssl http2;
server_name auth.example.com;
ssl_certificate /etc/nginx/certs/tls.crt;
ssl_certificate_key /etc/nginx/certs/tls.key;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
# Buffer sizes cho Keycloak (tokens có thể lớn)
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
large_client_header_buffers 4 16k;
location / {
proxy_pass https://keycloak_backend;
# X-Forwarded headers
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;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# WebSocket support (cho Admin Console)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
5.3 HTTP/2 のサポート
# Enable HTTP/2 (default đã enabled khi dùng HTTPS)
bin/kc.sh start --optimized \
--hostname=auth.example.com \
--https-certificate-file=/certs/tls.crt \
--https-certificate-key-file=/certs/tls.key \
--http-enabled=false
# Nếu cần HTTP/2 cleartext (h2c) cho internal communication
bin/kc.sh start --optimized \
--hostname=auth.example.com \
--http-enabled=true \
--http-port=8080
6. JVMのチューニング
6.1 ヒープ構成
Keycloak は Quarkus/JVM 上で実行されるため、JVM のチューニングはパフォーマンスと安定性に直接影響します。
# JVM Heap - sử dụng JAVA_OPTS_KC_HEAP (Keycloak 25+)
export JAVA_OPTS_KC_HEAP="-XX:InitialRAMPercentage=50.0 -XX:MaxRAMPercentage=70.0"
# Hoặc set fixed heap size
export JAVA_OPTS_KC_HEAP="-Xms512m -Xmx2g"
6.2 ガベージ コレクターの選択
# Option 1: G1GC (recommended cho heap ≤ 4GB)
export JAVA_OPTS_APPEND="-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:G1HeapRegionSize=16m \
-XX:+ParallelRefProcEnabled \
-XX:+UseStringDeduplication"
# Option 2: ZGC (recommended cho heap > 4GB, low-latency)
export JAVA_OPTS_APPEND="-XX:+UseZGC \
-XX:+ZGenerational \
-XX:ConcGCThreads=2"
# Option 3: Shenandoah GC (alternative low-latency)
export JAVA_OPTS_APPEND="-XX:+UseShenandoahGC \
-XX:ShenandoahGCHeuristics=compact"
| GCアルゴリズム | 最適な用途 | ヒープサイズ | 一時停止時間 |
|---|---|---|---|
| G1GC | 汎用、バランスの取れたスループット/レイテンシー | ≤ 4GB | ~200ms |
| ZGC | 低レイテンシー、大規模ヒープ | > 4ギガバイト | <; 1ms |
| シェナンドー | 低遅延、同時実行 | > 2GB | <; 10ミリ秒 |
6.3 コンテナ対応の JVM 設定
# Container-aware JVM settings (Docker/Kubernetes)
export JAVA_OPTS_APPEND=" \
-XX:+UseContainerSupport \
-XX:MaxRAMPercentage=70.0 \
-XX:InitialRAMPercentage=50.0 \
-XX:MinRAMPercentage=50.0 \
-XX:ActiveProcessorCount=2 \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-Djava.net.preferIPv4Stack=true \
-Djava.awt.headless=true \
-Dfile.encoding=UTF-8"
6.4 完全な JVM 構成
# Production JVM configuration hoàn chỉnh
export JAVA_OPTS_KC_HEAP="-XX:InitialRAMPercentage=50.0 -XX:MaxRAMPercentage=70.0"
export JAVA_OPTS_APPEND=" \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:G1HeapRegionSize=16m \
-XX:+ParallelRefProcEnabled \
-XX:+UseStringDeduplication \
-XX:+UseContainerSupport \
-XX:ActiveProcessorCount=2 \
-XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=512m \
-Djava.net.preferIPv4Stack=true \
-Djava.awt.headless=true \
-Dfile.encoding=UTF-8 \
-XX:+ExitOnOutOfMemoryError \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/opt/keycloak/dumps/"
7. Quarkus スレッド プールと Vert.x
Keycloak は Quarkus (Vert.x イベント ループ + ワーカー スレッド プール) 上で実行されます。このパターンを理解すると、正しいチューニングに役立ちます。
┌───────────────────────────────────────────────────────┐
│ Keycloak / Quarkus │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Vert.x Event Loop Threads │ │
│ │ (IO_THREADS = 2 × CPU cores, default) │ │
│ │ - Accept connections │ │
│ │ - Parse HTTP requests │ │
│ │ - Non-blocking operations │ │
│ └──────────────┬──────────────────────────────────┘ │
│ │ delegate blocking work │
│ ┌──────────────▼──────────────────────────────────┐ │
│ │ Worker Thread Pool │ │
│ │ (WORKER_THREADS = 8 × CPU cores, default) │ │
│ │ - Database queries │ │
│ │ - LDAP calls │ │
│ │ - Token signing/validation │ │
│ │ - Template rendering │ │
│ └─────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────┘
# Quarkus thread pool configuration (qua Java system properties)
export JAVA_OPTS_APPEND=" \
-Dquarkus.thread-pool.max-threads=200 \
-Dquarkus.thread-pool.queue-size=1000 \
-Dquarkus.thread-pool.growth-resistance=0.1 \
-Dquarkus.vertx.event-loops-pool-size=4"
| パラメータ | デフォルト | 説明する |
|---|---|---|
quarkus.thread-pool.max-threads | 200 (または 8 × CPU 最大) | 最大ワーカースレッド |
quarkus.thread-pool.queue-size | 無限の | すべてのスレッドがビジー状態のときのキュー サイズ |
quarkus.thread-pool.growth-resistance | 0 | 0 ~ 1、スレッド プールの抵抗が増加します |
quarkus.vertx.event-loops-pool-size | 2 × CPU | Vert.x イベント ループ スレッド |
8. Infinispan ローカル キャッシュのチューニング
8.1 Keycloakキャッシュアーキテクチャ
Keycloakは、Infinispanローカルキャッシュを使用してメタデータ(レルム、ユーザー、キーなど)をキャッシュし、データベースクエリを削減します。キャッシュ サイズと寿命の調整はパフォーマンスに大きく影響します。
┌─────────────────────────────────────────────────────────────┐
│ Infinispan Local Caches │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ realms │ │ users │ │ authorization │ │
│ │ (realm cfg) │ │ (user data) │ │ (permissions) │ │
│ │ max: 10000 │ │ max: 10000 │ │ max: 10000 │ │
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────────────────────────────┐ │
│ │ keys │ │ Revision Caches │ │
│ │ (crypto) │ │ (realmRevisions, userRevisions, │ │
│ │ max: 1000 │ │ authorizationRevisions) │ │
│ └──────────────┘ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
8.2 カスタムキャッシュ構成
ファイルの作成キャッシュ-ispn.xmlカスタム:
<?xml version="1.0" encoding="UTF-8"?>
<infinispan
xmlns="urn:infinispan:config:15.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:infinispan:config:15.0
https://infinispan.org/schemas/infinispan-config-15.0.xsd">
<cache-container name="keycloak">
<!-- Realm cache - cache metadata realm -->
<local-cache name="realms">
<encoding>
<key media-type="application/x-java-object"/>
<value media-type="application/x-java-object"/>
</encoding>
<memory max-count="20000"/>
</local-cache>
<!-- User cache - cache user data -->
<local-cache name="users">
<encoding>
<key media-type="application/x-java-object"/>
<value media-type="application/x-java-object"/>
</encoding>
<memory max-count="20000"/>
</local-cache>
<!-- Authorization cache -->
<local-cache name="authorization">
<encoding>
<key media-type="application/x-java-object"/>
<value media-type="application/x-java-object"/>
</encoding>
<memory max-count="20000"/>
</local-cache>
<!-- Keys cache - signing/encryption keys -->
<local-cache name="keys">
<encoding>
<key media-type="application/x-java-object"/>
<value media-type="application/x-java-object"/>
</encoding>
<memory max-count="5000"/>
<expiration max-idle="3600000"/> <!-- 1 hour -->
</local-cache>
<!-- Revision caches - invalidation tracking -->
<local-cache name="realmRevisions">
<encoding>
<key media-type="application/x-java-object"/>
<value media-type="application/x-java-object"/>
</encoding>
<memory max-count="40000"/>
</local-cache>
<local-cache name="userRevisions">
<encoding>
<key media-type="application/x-java-object"/>
<value media-type="application/x-java-object"/>
</encoding>
<memory max-count="40000"/>
</local-cache>
<local-cache name="authorizationRevisions">
<encoding>
<key media-type="application/x-java-object"/>
<value media-type="application/x-java-object"/>
</encoding>
<memory max-count="40000"/>
</local-cache>
</cache-container>
</infinispan>
# Sử dụng custom cache config
bin/kc.sh build --cache=ispn --cache-config-file=cache-ispn.xml
bin/kc.sh start --optimized
9. メトリクスとヘルスチェック
9.1 メトリックエンドポイント
# Enable metrics (cần set khi build)
bin/kc.sh build --metrics-enabled=true
bin/kc.sh start --optimized
メトリクスエンドポイント:https://auth.example.com/metrics (プロメテウス形式)
# Ví dụ metrics output
# HELP keycloak_logins_total Total successful logins
# TYPE keycloak_logins_total counter
keycloak_logins_total{realm="my-realm",provider="keycloak",client_id="my-app"} 1523
# HELP keycloak_failed_login_attempts_total Total failed login attempts
# TYPE keycloak_failed_login_attempts_total counter
keycloak_failed_login_attempts_total{realm="my-realm",provider="keycloak",client_id="my-app",error="invalid_user_credentials"} 42
# HELP vendor_memoryPool_usage_max_bytes Peak JVM memory pool usage
# TYPE vendor_memoryPool_usage_max_bytes gauge
vendor_memoryPool_usage_max_bytes{name="G1 Old Gen"} 524288000
# HELP vendor_cpu_processCpuLoad JVM Process CPU load
# TYPE vendor_cpu_processCpuLoad gauge
vendor_cpu_processCpuLoad 0.15
9.2 ヘルスチェックエンドポイント
# Enable health checks (cần set khi build)
bin/kc.sh build --health-enabled=true
| 終点 | 目的 | Kubernetes プローブ |
|---|---|---|
/健康/準備完了 | 準備状況 - トラフィックを受信する準備ができているか | レディネスプローブ |
/健康/ライブ | Liveness - プロセスはまだアクティブです | ライブネスプローブ |
/健康/開始しました | スタートアップ - 起動が完了しました | スタートアッププローブ |
/健康 | 総合的な健康状態 | — |
// GET /health/ready - Response khi healthy
{
"status": "UP",
"checks": [
{
"name": "Keycloak database connections health check",
"status": "UP"
}
]
}
// GET /health/ready - Response khi unhealthy
{
"status": "DOWN",
"checks": [
{
"name": "Keycloak database connections health check",
"status": "DOWN",
"data": {
"message": "Unable to connect to database"
}
}
]
}
10. ガトリングによる負荷テスト
10.1 Keycloakベンチマークプロジェクト
Keycloak は、Gatling フレームワークに基づいた公式ベンチマーク プロジェクトを提供します。
# Clone Keycloak benchmark
git clone https://github.com/keycloak/keycloak-benchmark.git
cd keycloak-benchmark
# Build benchmark
mvn clean install -DskipTests
# Run token endpoint benchmark
cd benchmark
mvn gatling:test \
-Dgatling.simulationClass=keycloak.scenario.authentication.ClientSecret \
-Dkeycloak.server.url=https://auth.example.com \
-Drealm=benchmark-realm \
-DclientId=benchmark-client \
-DclientSecret=benchmark-secret \
-DusersPerSec=50 \
-DrampUpPeriod=30 \
-DwarmUpPeriod=60 \
-DmeasurementPeriod=180
10.2 カスタムガトリングシミュレーション
// KeycloakLoadTest.scala
package keycloak.benchmark
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
class KeycloakTokenEndpointSimulation extends Simulation {
val keycloakUrl = System.getProperty("keycloak.url", "https://auth.example.com")
val realm = System.getProperty("realm", "my-realm")
val clientId = System.getProperty("clientId", "benchmark-client")
val clientSecret = System.getProperty("clientSecret", "benchmark-secret")
val httpProtocol = http
.baseUrl(keycloakUrl)
.acceptHeader("application/json")
.contentTypeHeader("application/x-www-form-urlencoded")
.disableFollowRedirect
// Scenario 1: Client Credentials Grant (service-to-service)
val clientCredentialsScenario = scenario("Client Credentials Flow")
.exec(
http("Token Request - Client Credentials")
.post(s"/realms/$realm/protocol/openid-connect/token")
.formParam("grant_type", "client_credentials")
.formParam("client_id", clientId)
.formParam("client_secret", clientSecret)
.check(status.is(200))
.check(jsonPath("$.access_token").exists)
.check(responseTimeInMillis.lte(500))
)
// Scenario 2: Resource Owner Password Grant (user login)
val passwordGrantScenario = scenario("Resource Owner Password Flow")
.exec(
http("Token Request - Password Grant")
.post(s"/realms/$realm/protocol/openid-connect/token")
.formParam("grant_type", "password")
.formParam("client_id", clientId)
.formParam("client_secret", clientSecret)
.formParam("username", "test-user")
.formParam("password", "test-password")
.check(status.is(200))
.check(jsonPath("$.access_token").saveAs("accessToken"))
)
.pause(1.second)
.exec(
http("Token Introspection")
.post(s"/realms/$realm/protocol/openid-connect/token/introspect")
.formParam("token", "${accessToken}")
.formParam("client_id", clientId)
.formParam("client_secret", clientSecret)
.check(status.is(200))
.check(jsonPath("$.active").is("true"))
)
// Scenario 3: UserInfo endpoint
val userInfoScenario = scenario("UserInfo Flow")
.exec(
http("Get Token")
.post(s"/realms/$realm/protocol/openid-connect/token")
.formParam("grant_type", "password")
.formParam("client_id", clientId)
.formParam("client_secret", clientSecret)
.formParam("username", "test-user")
.formParam("password", "test-password")
.check(status.is(200))
.check(jsonPath("$.access_token").saveAs("accessToken"))
)
.exec(
http("UserInfo")
.get(s"/realms/$realm/protocol/openid-connect/userinfo")
.header("Authorization", "Bearer ${accessToken}")
.check(status.is(200))
)
setUp(
clientCredentialsScenario.inject(
rampUsersPerSec(1).to(100).during(60.seconds), // Ramp up
constantUsersPerSec(100).during(180.seconds), // Sustained load
rampUsersPerSec(100).to(1).during(30.seconds) // Ramp down
),
passwordGrantScenario.inject(
rampUsersPerSec(1).to(50).during(60.seconds),
constantUsersPerSec(50).during(180.seconds),
rampUsersPerSec(50).to(1).during(30.seconds)
)
).protocols(httpProtocol)
.assertions(
global.responseTime.percentile3.lt(500), // p95 < 500ms
global.responseTime.percentile4.lt(1000), // p99 < 1000ms
global.successfulRequests.percent.gt(99) // > 99% success rate
)
}
11. 生産チェックリストの概要
| カテゴリ | アイテム | 状態 |
|---|---|---|
| データベース | 接続プールが調整された PostgreSQL | ☐ |
| データベース | データベースの自動バックアップ (pg_dump / pg_basebackup) | ☐ |
| データベース | データベースのレプリケーションが構成されている | ☐ |
| 建てる | kc.sh ビルド + 開始 --最適化済み | ☐ |
| 建てる | 多段階の Dockerfile | ☐ |
| ホスト名 | --ホスト名構成済み (ホスト名-v2) | ☐ |
| ホスト名 | --ホスト名-管理者別のドメインに設定する | ☐ |
| TLS | 有効な TLS 証明書 (Let's Encrypt / CA 署名付き) | ☐ |
| TLS | TLS 1.2+ を適用、弱い暗号は無効化 | ☐ |
| プロキシ | --プロキシヘッダー正しく設定されている | ☐ |
| JVM | ヒープ サイズ (コンテナー メモリの 50 ~ 70%) | ☐ |
| JVM | 選択された GC アルゴリズム (G1GC/ZGC) | ☐ |
| JVM | コンテナ対応フラグが有効になっています | ☐ |
| JVM | -XX:+ExitOnOutOfMemoryErrorセット | ☐ |
| キャッシュ | Infinispan ローカル キャッシュの調整 | ☐ |
| 可観測性 | メトリクスエンドポイントが有効になっています (/メトリクス) | ☐ |
| 可観測性 | ヘルスチェックが有効になっています (/健康/*) | ☐ |
| 可観測性 | ログの構成 (JSON 形式、ログ レベル) | ☐ |
| 安全 | 別のドメイン/ネットワーク上の管理コンソール | ☐ |
| 安全 | デフォルトの管理者の資格情報が変更されました | ☐ |
| 安全 | ブルートフォース保護が有効になっています | ☐ |
| 安全 | CORS が正しく構成されている | ☐ |
| 負荷テスト | ガトリング ベンチマークに合格 (p95 < 500ms) | ☐ |
| 負荷テスト | 容量計画の文書化 | ☐ |
| バックアップ | レルムエクスポートの自動化 | ☐ |
| バックアップ | 災害復旧計画のテスト済み | ☐ |
# Complete production start command
bin/kc.sh start --optimized \
--db=postgres \
--db-url="jdbc:postgresql://db-host:5432/keycloak?ssl=true" \
--db-username=keycloak \
--db-password="${KC_DB_PASSWORD}" \
--db-pool-initial-size=25 \
--db-pool-min-size=25 \
--db-pool-max-size=100 \
--hostname=auth.example.com \
--hostname-admin=admin-auth.internal.example.com \
--hostname-strict=true \
--proxy-headers=xforwarded \
--https-certificate-file=/certs/tls.crt \
--https-certificate-key-file=/certs/tls.key \
--http-enabled=false \
--health-enabled=true \
--metrics-enabled=true \
--log=console \
--log-level=info \
--log-console-output=json
# Docker Compose cho production
# docker-compose.production.yml
version: "3.9"
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- pgdata:/var/lib/postgresql/data
- ./postgresql.conf:/etc/postgresql/postgresql.conf
command: postgres -c config_file=/etc/postgresql/postgresql.conf
secrets:
- db_password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U keycloak"]
interval: 10s
timeout: 5s
retries: 5
deploy:
resources:
limits:
cpus: "2.0"
memory: 4G
keycloak:
image: my-registry/keycloak-production:26.0
build:
context: .
dockerfile: Dockerfile.keycloak
environment:
KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD_FILE: /run/secrets/db_password
KC_DB_POOL_INITIAL_SIZE: "25"
KC_DB_POOL_MIN_SIZE: "25"
KC_DB_POOL_MAX_SIZE: "100"
KC_HOSTNAME: auth.example.com
KC_HOSTNAME_ADMIN: admin-auth.internal.example.com
KC_PROXY_HEADERS: xforwarded
KC_HTTPS_CERTIFICATE_FILE: /certs/tls.crt
KC_HTTPS_CERTIFICATE_KEY_FILE: /certs/tls.key
KC_HTTP_ENABLED: "false"
KC_LOG: console
KC_LOG_LEVEL: info
KC_LOG_CONSOLE_OUTPUT: json
JAVA_OPTS_KC_HEAP: "-XX:InitialRAMPercentage=50.0 -XX:MaxRAMPercentage=70.0"
JAVA_OPTS_APPEND: >-
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+UseContainerSupport
-XX:+ExitOnOutOfMemoryError -Djava.net.preferIPv4Stack=true
volumes:
- ./certs:/certs:ro
secrets:
- db_password
ports:
- "8443:8443"
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -sf https://localhost:8443/health/ready || exit 1"]
interval: 15s
timeout: 5s
retries: 5
start_period: 120s
deploy:
resources:
limits:
cpus: "2.0"
memory: 2G
volumes:
pgdata:
secrets:
db_password:
file: ./secrets/db_password.txt