GitLab + docker registry + nginx リバースプロキシ + オレオレ証明書SSLをdocker-composeで構築

こちらの記事 でも利用させていただいた、 sameersbn / docker-gitlab とContainer Registryを連携して、さらにnginxでリバースプロキシしてついでにSSL化したいと思います。ただし、オレオレ証明書です。

今回はCentOSの仮想マシン上で構築してみます。
一応実行環境はこんな感じ。

$ cat /etc/redhat-release 
CentOS Linux release 7.7.1908 (Core)
$ docker --version
Docker version 19.03.5, build 633a0ea
$ docker-compose --version
docker-compose version 1.25.0, build 0a186604

ではまず普通にdocker-composeでGitLabを構築します。
この時、「GITLAB_RELATIVE_URL_ROOT=/gitlab」にしといてください。
最後にdocker-compose.ymlを丸ごと載せてるのでそちらを見ながら読んでもらうといいかもです。

$ docker-compose up -d redis postgresql
$ docker-compose up -d gitlab

結構時間かかると思います。気長に待ちましょう。
無事起動したらhttp://[仮想マシンのIP]:10080/gitlabにアクセスして、
rootのパスワード設定しておいてください。

gitlabregistry_001.png

続いて証明書を作成します。
今回はdocker-compose.ymlがあるディレクトリにcertsディレクトリを作成し、
そこに証明書を作成します。
また、Chromeで怒られないようにSANを含めます。
詳しくはこちら のサイト様を参照ください。

$ mkdir certs
$ cd certs/
$ openssl genrsa 2048 > server.key
Generating RSA private key, 2048 bit long modulus
..............................+++
.............+++
e is 65537 (0x10001)
$ openssl req -new -key server.key > server.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:JP
State or Province Name (full name) []:
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
$ echo subjectAltName=DNS:*.tsuchinokometal.com > san.ext
$ openssl x509 -days 3650 -req -signkey server.key < server.csr > server.crt -extfile san.ext
Signature ok
subject=/C=JP/L=Default City/O=Default Company Ltd
Getting Private key

続いて、nginx用の設定ファイルを先に作っておきます。

$ mkdir conf.d
$ vi conf.d/ssl.conf

ssl.confは以下の通り。
内容についてはほぼこちら と一緒。

server {
    root /dev/null;
    server_name example.tsuchinokometal.com;
    charset UTF-8;

    listen *:443 ssl http2;
    ssl_certificate /certs/server.crt;
    ssl_certificate_key /certs/server.key;

    ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4';
    ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_session_cache  builtin:1000  shared:SSL:10m;
    ssl_session_timeout  5m;

    client_max_body_size 0;
    chunked_transfer_encoding on;
    
    proxy_set_header  Host              $http_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_read_timeout                  900;    

    location /gitlab {
        proxy_pass http://gitlab;
    }
}
server {
    listen *:80;
    server_name  example.tsuchinokometal.com;
    server_tokens off;
    return 301 https://$http_host:$request_uri;
}

さらにdocker-compose.ymlを変更します。

- GITLAB_HTTPS=true
- GITLAB_HOST=example.tsuchinokometal.com

以下を追記します。

  nginx:
    image: nginx
    volumes:
      - ./conf.d:/etc/nginx/conf.d
      - ./certs:/certs
    ports:
      - "80:80"
      - "443:443"

GitLabの再作成およびnginxを起動します。

$ docker-compose stop gitlab
$ docker-compose up -d gitlab
$ docker-compose up -d nginx

アクセスするためにオレオレ証明書を信頼させてください。
僕はMacを使っているのでこちら を参考にさせていただきました。
あと、ドメインで名前解決できるようにhostsに追記しておいてください。
見ての通りexample.tsuchinokometal.comを想定して設定しています。
各自の環境に合わせて変更してください。

ここまでで、GitLabにhttpsでアクセスできると思いますが、どうでしょうか?
(リバースプロキシしてるんでポート指定が不要になります)

gitlabregistry_002.png

では、registryとの連携を構築します。
docker-compose.ymlを変更してください。
gitlabにcertsディレクトリをマウントしてregistryの設定を追記します。

    volumes:
    - ./certs:/certs
    environment:
    - GITLAB_REGISTRY_ENABLED=true
    - GITLAB_REGISTRY_HOST=example.tsuchinokometal.com
    - GITLAB_REGISTRY_PORT=443
    - GITLAB_REGISTRY_API_URL=http://registry:5000
    - GITLAB_REGISTRY_KEY_PATH=/certs/server.key

registryを追記します。 volumesにregistry-dataも追記しといてください。

  registry:
    restart: always
    image: registry:2
    expose:
      - "5000"
    ports:
      - "5000:5000"
    volumes:
      - registry-data:/registry
      - ./certs:/certs
    environment:
      - REGISTRY_LOG_LEVEL=info
      - REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/registry
      - REGISTRY_AUTH_TOKEN_REALM=https://example.tsuchinokometal.com/gitlab/jwt/auth
      - REGISTRY_AUTH_TOKEN_SERVICE=container_registry
      - REGISTRY_AUTH_TOKEN_ISSUER=gitlab-issuer
      - REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE=/certs/server.crt
      - REGISTRY_STORAGE_DELETE_ENABLED=true

さらに、nginxに以下を追記してください。

    location /v2/ {
        proxy_pass http://registry:5000;
    }

コンテナを再起動したら、GitLabにログインして適当なプロジェクトを作成し、Container Registryを開いてみてください。
以下のように表示されれば成功です。

gitlabregistry_003.png

Macからregistryの動作確認します。 まずregistryにログインします。

% docker login example.tsuchinokometal.com
Username: root
Password: 
Login Succeeded

このときこんなエラーが出た場合、Macの方のDockerを再起動してください。

Login did not succeed, error: Error response from daemon: Get https://example.tsuchinokometal.com/v2/: x509: certificate signed by unknown authority

適当なコンテナにタグ付けしてプッシュします。

% docker pull alpine:latest
latest: Pulling from library/alpine
c9b1b535fdd9: Pull complete 
Digest: sha256:ab00606a42621fb68f2ed6ad3c88be54397f981a7b70a79db3d1172b11c4367d
Status: Downloaded newer image for alpine:latest
docker.io/library/alpine:latest
% docker tag alpine:latest example.tsuchinokometal.com/root/test/hoge:fuga
% docker push example.tsuchinokometal.com/root/test/hoge:fuga
The push refers to repository [example.tsuchinokometal.com/root/test/hoge]
5216338b40a7: Pushed 
fuga: digest: sha256:ddba4d27a7ffc3f86dd6c2f92041af252a1f23a8e742c90e6e1297bfa1bc0c45 size: 528

GitLab側でこんなふうに表示されます。

gitlabregistry_004.png

GitLabと連携させればソースコードと一緒にコンテナイメージが管理できますし、 GitLabのユーザで認証も付けられるのでかなり管理しやすいんじゃないでしょうか。

最終的に、docker-compose.ymlは以下のようになりました。 ここではやりませんでしたが、共有ネットワーク作ってdocker-compose.ymlを分割した方が管理しやすいかもですね。

version: '3'

services:
  redis:
    restart: always
    image: sameersbn/redis:4.0.9-2
    command:
    - --loglevel warning
    volumes:
    - redis-data:/var/lib/redis:Z

  postgresql:
    restart: always
    image: sameersbn/postgresql:10-2
    volumes:
    - postgresql-data:/var/lib/postgresql:Z
    environment:
    - DB_USER=gitlab
    - DB_PASS=password
    - DB_NAME=gitlabhq_production
    - DB_EXTENSION=pg_trgm

  gitlab:
    restart: always
    image: sameersbn/gitlab:12.5.5
    depends_on:
    - redis
    - postgresql
    ports:
    - "10080:80"
    - "10022:22"
    volumes:
    - gitlab-data:/home/git/data:Z
    - ./certs:/certs
    environment:
    - GITLAB_REGISTRY_ENABLED=true
    - GITLAB_REGISTRY_HOST=example.tsuchinokometal.com
    - GITLAB_REGISTRY_PORT=443
    - GITLAB_REGISTRY_API_URL=http://registry:5000
    - GITLAB_REGISTRY_KEY_PATH=/certs/server.key

    - DEBUG=false

    - DB_ADAPTER=postgresql
    - DB_HOST=postgresql
    - DB_PORT=5432
    - DB_USER=gitlab
    - DB_PASS=password
    - DB_NAME=gitlabhq_production

    - REDIS_HOST=redis
    - REDIS_PORT=6379

    - TZ=Asia/Tokyo
    - GITLAB_TIMEZONE=Tokyo

    - GITLAB_HTTPS=true
    - SSL_SELF_SIGNED=false

    - GITLAB_HOST=example.tsuchinokometal.com
    - GITLAB_PORT=10080
    - GITLAB_SSH_PORT=10022
    - GITLAB_RELATIVE_URL_ROOT=/gitlab
    - GITLAB_SECRETS_DB_KEY_BASE=Nz7kgKFT9CzgscN7Wzj9chCp3KRk77ghr4wqmTHbdRxWw44Kmc9WpvPqfjFhkXM7
    - GITLAB_SECRETS_SECRET_KEY_BASE=hPFsKkX4kqftKJpMwgnpvnqKwqsWmkfPsN3F4xTCbTghJ77qvnxdFkxkt3XX3PFT
    - GITLAB_SECRETS_OTP_KEY_BASE=HXWdMCLhkWwLLMCnJLNK4sLhPk9VsskrvJpppCFfhRhfb9fMd9hdqWJp9tgCXXvz

    - GITLAB_ROOT_PASSWORD=
    - GITLAB_ROOT_EMAIL=

    - GITLAB_NOTIFY_ON_BROKEN_BUILDS=true
    - GITLAB_NOTIFY_PUSHER=false

    - GITLAB_EMAIL=notifications@example.com
    - GITLAB_EMAIL_REPLY_TO=noreply@example.com
    - GITLAB_INCOMING_EMAIL_ADDRESS=reply@example.com

    - GITLAB_BACKUP_SCHEDULE=daily
    - GITLAB_BACKUP_TIME=01:00

    - SMTP_ENABLED=false
    - SMTP_DOMAIN=www.example.com
    - SMTP_HOST=smtp.gmail.com
    - SMTP_PORT=587
    - SMTP_USER=mailer@example.com
    - SMTP_PASS=password
    - SMTP_STARTTLS=true
    - SMTP_AUTHENTICATION=login

    - IMAP_ENABLED=false
    - IMAP_HOST=imap.gmail.com
    - IMAP_PORT=993
    - IMAP_USER=mailer@example.com
    - IMAP_PASS=password
    - IMAP_SSL=true
    - IMAP_STARTTLS=false

    - OAUTH_ENABLED=false
    - OAUTH_AUTO_SIGN_IN_WITH_PROVIDER=
    - OAUTH_ALLOW_SSO=
    - OAUTH_BLOCK_AUTO_CREATED_USERS=true
    - OAUTH_AUTO_LINK_LDAP_USER=false
    - OAUTH_AUTO_LINK_SAML_USER=false
    - OAUTH_EXTERNAL_PROVIDERS=

    - OAUTH_CAS3_LABEL=cas3
    - OAUTH_CAS3_SERVER=
    - OAUTH_CAS3_DISABLE_SSL_VERIFICATION=false
    - OAUTH_CAS3_LOGIN_URL=/cas/login
    - OAUTH_CAS3_VALIDATE_URL=/cas/p3/serviceValidate
    - OAUTH_CAS3_LOGOUT_URL=/cas/logout

    - OAUTH_GOOGLE_API_KEY=
    - OAUTH_GOOGLE_APP_SECRET=
    - OAUTH_GOOGLE_RESTRICT_DOMAIN=

    - OAUTH_FACEBOOK_API_KEY=
    - OAUTH_FACEBOOK_APP_SECRET=

    - OAUTH_TWITTER_API_KEY=
    - OAUTH_TWITTER_APP_SECRET=

    - OAUTH_GITHUB_API_KEY=
    - OAUTH_GITHUB_APP_SECRET=
    - OAUTH_GITHUB_URL=
    - OAUTH_GITHUB_VERIFY_SSL=

    - OAUTH_GITLAB_API_KEY=
    - OAUTH_GITLAB_APP_SECRET=

    - OAUTH_BITBUCKET_API_KEY=
    - OAUTH_BITBUCKET_APP_SECRET=

    - OAUTH_SAML_ASSERTION_CONSUMER_SERVICE_URL=
    - OAUTH_SAML_IDP_CERT_FINGERPRINT=
    - OAUTH_SAML_IDP_SSO_TARGET_URL=
    - OAUTH_SAML_ISSUER=
    - OAUTH_SAML_LABEL="Our SAML Provider"
    - OAUTH_SAML_NAME_IDENTIFIER_FORMAT=urn:oasis:names:tc:SAML:2.0:nameid-format:transient
    - OAUTH_SAML_GROUPS_ATTRIBUTE=
    - OAUTH_SAML_EXTERNAL_GROUPS=
    - OAUTH_SAML_ATTRIBUTE_STATEMENTS_EMAIL=
    - OAUTH_SAML_ATTRIBUTE_STATEMENTS_NAME=
    - OAUTH_SAML_ATTRIBUTE_STATEMENTS_USERNAME=
    - OAUTH_SAML_ATTRIBUTE_STATEMENTS_FIRST_NAME=
    - OAUTH_SAML_ATTRIBUTE_STATEMENTS_LAST_NAME=

    - OAUTH_CROWD_SERVER_URL=
    - OAUTH_CROWD_APP_NAME=
    - OAUTH_CROWD_APP_PASSWORD=

    - OAUTH_AUTH0_CLIENT_ID=
    - OAUTH_AUTH0_CLIENT_SECRET=
    - OAUTH_AUTH0_DOMAIN=
    - OAUTH_AUTH0_SCOPE=

    - OAUTH_AZURE_API_KEY=
    - OAUTH_AZURE_API_SECRET=
    - OAUTH_AZURE_TENANT_ID=

  nginx:
    image: nginx
    volumes:
      - ./conf.d:/etc/nginx/conf.d
      - ./certs:/certs
    ports:
      - "80:80"
      - "443:443"

  registry:
    restart: always
    image: registry:2
    expose:
      - "5000"
    ports:
      - "5000:5000"
    volumes:
      - registry-data:/registry
      - ./certs:/certs
    environment:
      - REGISTRY_LOG_LEVEL=info
      - REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/registry
      - REGISTRY_AUTH_TOKEN_REALM=https://example.tsuchinokometal.com/gitlab/jwt/auth
      - REGISTRY_AUTH_TOKEN_SERVICE=container_registry
      - REGISTRY_AUTH_TOKEN_ISSUER=gitlab-issuer
      - REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE=/certs/server.crt
      - REGISTRY_STORAGE_DELETE_ENABLED=true
volumes:
  redis-data:
  postgresql-data:
  gitlab-data:
  registry-data:

一応ssl.confはこんな感じ。

server {
    root /dev/null;
    server_name example.tsuchinokometal.com;
    charset UTF-8;

    listen *:443 ssl http2;
    ssl_certificate /certs/server.crt;
    ssl_certificate_key /certs/server.key;

    ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4';
    ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_session_cache  builtin:1000  shared:SSL:10m;
    ssl_session_timeout  5m;

    client_max_body_size 0;
    chunked_transfer_encoding on;
    
    proxy_set_header  Host              $http_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_read_timeout                  900;    

    location /gitlab {
        proxy_pass http://gitlab;
    }

    location /v2/ {
        proxy_pass http://registry:5000;
    }
}
server {
    listen *:80;
    server_name  example.tsuchinokometal.com;
    server_tokens off;
    return 301 https://$http_host:$request_uri;
}