🐳

Jenkins + NGINX + SSL + Docker で同じURLドメインのパスでルーティングする

2020/11/27に公開

背景

NGINXをリバースプロキシとして、複数のコンテナに異なるドメインで振り分ける構成の記事は見つけたのですが、パスで異なるコンテナにルーティング & SSL通信する記事が見当たらなかったので備忘として残しておきます。もし同じような構成にする方がいればご参考いただけると幸いです。
(間違ってるところがあればご指摘お願いいたします)

制限事項

Jenkinsを使ってビルドする際に、同じドメインを使っているアプリケーションのコンテナを再起動したりすると、その再起動中はJenkinsもHTTP通信が途絶えます...。(いい方法があったら教えてください🙇‍♂️)

環境

OS

GCP の Container-Optimized OS
cos-81-lts

Docker

19.03.6
※ docker-composeを使います。
docker/compose:1.24.0

アーキテクチャ

アプリサーバーのコンテナがあるインスタンス上に、Jenkinsのコンテナも配置します。/ciというパスでJenkinsコンテナにルーティングして、それ以外はアプリケーションのコンテナに向くようにします。

構築手順

Jenkins

Jenkinsのserviceは以下のようになりました。

docker-compose.jenkins.yml
version: "3"

services:
  jenkins:
    image: jenkins/jenkins:lts
    environment:
      - VIRTUAL_HOST=ci.example.jp        # (1)
      - VIRTUAL_PORT=8080
      - JENKINS_OPTS="--prefix=/ci"
    ports: 
      - "8080:8080"
      - "50000:50000"
    volumes:
      - ./jenkins_home:/var/jenkins       # (2)
    networks:
      - reverse-proxy

networks:
  reverse-proxy:                          # (3)
    external: true

(1) VIRTUAL_HOSTを環境変数にセットすることで、後述するNGINXコンテナnginx-proxyが振り分けれるようにしてます。

(2) ホストの./jenkins_homeディレクトリとJenkinsコンテナ上の/var/jenkins_homeディレクトリを同期してます。Jenkinsのデータを永続化するためです。

(3) ネットワークは既存のものを使うようにしています。以降のコンテナもすべて同じネットワークを使います。

NGINX

リバースプロキシです。

docker-compose.nginx.yml
  nginx:
    image: jwilder/nginx-proxy
    container_name: nginx-proxy
    environment: 
      - NGINX_PROXY_CONTAINER=nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - certs:/etc/nginx/certs:ro
      - vhost:/etc/nginx/vhost.d
      - nginx-html:/usr/share/nginx/html
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./proxy:/etc/nginx/vhost.d
      - ./proxy/app.example.jp:/etc/nginx/vhost.d/app.example.jp # (1)
      - ./proxy/ci.example.jp:/etc/nginx/vhost.d/ci.example.jp   # (2)
    restart: always
    labels:
      - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy=true"
    networks:
      - reverse-proxy

volumes:
  certs:
  vhost:
  nginx-html:

アプリケーションコンテナのVIRTUAL_HOSTと同じ名前のバーチャルホストの設定ファイル(ここではapp.example.jpci.example.jp)をマウントすると、自動でincludeしてくれます。ためしに見てみます。

$ docker exec -it nginx-proxy /bin/bash

NGINXプロキシのコンテナにはいって、default.confをのぞくと

# cat /etc/nginx/conf.d/default.conf 
server {
	server_name ci.example.jp;
	# ...
	include /etc/nginx/vhost.d/ci.example.jp;
	# ...
}
server {
	server_name app.example.jp;
	# ...
	include /etc/nginx/vhost.d/app.example.jp;
	# ...
}

includeされてました。

(1) アプリケーションのバーチャルホスト設定 app.example.jp

Jenkinsのリファレンスを参考に、バーチャルホストの設定ファイルは以下のようにしました。

app.example.jp
## (1-1) Lets encrypt(SSL)のための設定
location ^~ /.well-known/acme-challenge/ {
    auth_basic off;
    auth_request off;
    allow all;
    root /usr/share/nginx/html;
    try_files $uri =404;
    break;
}

## (1-2) Jenkinsのための設定
### (1-2-1) トレイリングスラッシュあり
location ^~ /ci/ {
    sendfile off;
    ### (1-2-2) upstream(=VIRTUAL_HOST) と同じにする
    proxy_pass         http://ci.example.jp; 
    proxy_redirect     default;
    proxy_http_version 1.1;

    # Required for Jenkins websocket agents
    ### (1-2-3) 変数をあわせる
    proxy_set_header   Connection        $proxy_connection;
    proxy_set_header   Upgrade           $http_upgrade;

    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_max_temp_file_size 0;

    #this is the maximum upload size
    client_max_body_size       10m;
    client_body_buffer_size    128k;

    proxy_connect_timeout      90;
    proxy_send_timeout         90;
    proxy_read_timeout         90;
    proxy_buffering            off;
    proxy_request_buffering    off; # Required for HTTP CLI commands
    proxy_set_header Connection ""; # Clear for keepalive
}
(1-1) Let's encrypt(SSL)のための設定

本来は、後述のLet's encrypt companionコンテナを起動すると自動で書き出される設定なのですが、SSL設定したいドメインのバーチャルホスト設定ファイル(ここではapp.example.jp)をマウントすると、設定されなかったのであらかじめ記載しました。(ほかにやり方が思いつきませんでした...)

(1-2) Jenkinsのための設定
(1-2-1) トレイリングスラッシュあり

末尾のスラッシュをつけることで、/ciのパスを無視するようにしてます。

(1-2-2) upstream(=VIRTUAL_HOST) と同じにする

振り分け先のドメインにあわせてhttp://ci.example.jpにしました。自動生成されていたnginx設定ファイルdefault.confの該当箇所は以下です。

default.conf
# ci.example.jp
upstream ci.example.jp {
				## Can be connected with "reverse-proxy" network
			# jenkins
			server 172.18.0.3:8080;
}
(1-2-3) 変数をあわせる

WebSocketのための設定です。Jenkinsのリファレンスでは$connection_upgradeという変数名で書かれてますが、自動生成されるnginx設定ファイルdefault.confをみると、$proxy_connectionとなっていたので、そちらにあわせてます。

default.conf
map $http_upgrade $proxy_connection {
  default upgrade;
  '' close;
}

(2) Jenkinsのバーチャルホスト設定 ci.example.jp

静的ファイルへのアクセスはJenkins側のバーチャルホスト設定ファイルとして別で定義しました。

ci.example.jp
location ~ "^/static/[0-9a-fA-F]{8}\/(.*)$" {
    rewrite "^/static/[0-9a-fA-F]{8}\/(.*)" /$1 last;
}

location /userContent {
    root /var/jenkins_home/;
    if (!-f $request_filename){
        rewrite (.*) /$1 last;
        break;
    }
    sendfile on;
}

Let's encrypt

Let's encryptを使ってSSL証明書を発行してくれるコンテナです。
SSL証明書を発行する際に検証のためにつかうディレクトリや、証明書自体を保存しておくボリュームをvolumesに定義しています。ここがNGINXコンテナと記載があってないと、SSL証明書発行に失敗します。

docker-compose.le.yml
  letsencrypt:
    image: jrcs/letsencrypt-nginx-proxy-companion
    container_name: letsencrypt
    depends_on:
      - app
    volumes:
      - certs:/etc/nginx/certs:rw
      - vhost:/etc/nginx/vhost.d
      - nginx-html:/usr/share/nginx/html
      - /var/run/docker.sock:/var/run/docker.sock:ro
    restart: always
    networks:
      - reverse-proxy

アプリケーション

最後にアプリケーションのコンテナです。environmentでバーチャルホストやSSL証明書発行ための環境変数をセットしてます。

docker-compose.app.yml
  app:
    container_name: app
    environment:
      - VIRTUAL_HOST=app.example.jp
      - LETSENCRYPT_HOST=app.example.jp
      - LETSENCRYPT_EMAIL=test@test.test
    depends_on:
      - nginx
    networks:
      - reverse-proxy

以上です。

Discussion