日本語
[CI/CD] インスタンスからDooDのデプロイまでの流れ(Nginx、Jenkinsを活用)

[CI/CD] インスタンスからDooDのデプロイまでの流れ(Nginx、Jenkinsを活用)

DockerとNginxを活用したデプロイの基礎固め

23-06-16 ボリュームマウントを明確にするためにパスを修正 23-07-05 開発の利便性のために設定を調整

はじめに

[CI/CD] Docker + Jenkins + Git Webhook を使った FastAPI サーバの自動デプロイまとめ

今回この記事を書く理由は、上の前回記事で 1 次的に学ぼうとしていた内容を扱ったが、修正すべきところを修正し、整えるべきところを整えてきれいな 1 つの記事にまとめたかったからだ。

前回以降のデプロイ関連タスクや、周りの人のデプロイを手伝う中である程度のノウハウを身につけて定型化できたので、その記録を残し、次回以降のデプロイでも一貫したプロセスとして使えるようにしたい!本記事の手順に沿って進めれば、おそらくデプロイは難しくないはずだ。

1 編以降に学んだ追加の内容

  • Docker in Docker で実装するより Docker out of Docker で実装する理由は?
  • そもそも Docker は、Docker コンテナ上でコマンドを実行できるようにすることを推奨していない

  • Docker in Docker は Docker がインストールされたコンテナに非常に強い権限を付与することになり、セキュリティリスクを招き得る

  • その点 Docker out of Docker はコンテナ外部の Docker とソケットを共有して docker コマンドを実行する方式

  • Docker API を使う方式はどう?

実は 1 編で問題解決のために試した方式で、実際に効果があった。 ただし Docker out of Docker が無事ビルドできているなら、CI/CD の手順上、いまのところ大きな必要性は感じない…


別記事に分けるのは中途半端な気がしたので、簡単に記録した API 関連の内容

Docker は REST API を通じてエンジンとクライアントが通信するが、ほとんど使う機会はないとはいえ、使うことはできる。

  docker --tlsverify --tlscacert=/.docker/ca.pem \
  --tlscert=/.docker/cert.pem --tlskey=/.docker/key.pem \
  -H={server_ip}:{port_number} exec nginx \
  sed -i '4s/.*$/ server {container}:8000;/' /etc/nginx/sites-available/default.conf

このコードは TLS 証明書を持つ Docker Client を通じて、任意のサーバ上にある Nginx 設定ファイルの内容を修正するコードだ。 -H オプションでそのサーバの port を介して Daemon (Docker Engine) と通信し、残りのコードは証明書による接続セキュリティの手続きだ。

もし証明書を使わずポートを開けっ放しにすると、誰かに開いている port を通してコンテナの「托卵」を喰らう可能性がある!

コンテナ托卵の実体験。Docker でよく使われる port を認証なしで開けっ放しにした結果…

証明書によるセキュリティ処理は必ず行うべきで、やむを得ない事情で行えない場合は、その port を介して実行可能なコマンドを制限することが必須不可欠である。

[Docker API Documents] (https://docs.docker.com/engine/api/v1.43/) の内容を見ると、より詳しく確認できる!


All in One デプロイ基盤構築の手順

1. インスタンス接続とインスタンスのアップデート

ssh -i {secure shell key のパス} {ユーザー名}@{IP アドレス}

sudo apt-get update
sudo apt-get upgrade

ubuntu のセットアップを進める場合、ユーザー名が ubuntu であることが多い。

2. ファイアウォール設定

sudo ufw allow ssh    # ssh は 22 に置き換えても良い
sudo ufw allow http   # http は 80 に置き換えても良い
sudo ufw allow https  # https は 443 に置き換えても良い

sudo ufw enable 	  # ファイアウォールを起動
sudo ufw status 	  # ファイアウォールの状態確認

おそらくクラウドを使う場合、最初からクラウド独自のポートセキュリティ設定があるので、その設定でも開放しておく必要がある。 ufw は Ubuntu のファイアウォールで、最初に設定する際オフになっている場合は、ポートを許可した上で起動する手順が必要だ。 上の設定で、基本的な http (80)、https (443)、ssh (22) ポートを開放した状態でファイアウォールを起動できる。

必ず先にポートの allow 設定を行ってから起動することを推奨する。22 番、ssh ポートを閉じたままファイアウォールを起動すると、接続できなくなる…

3. Docker および Docker Compose のインストール

# Docker のインストール

sudo apt-get update
sudo apt-get install ca-certificates curl gnupg

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Docker Compose のインストール

sudo apt install docker-compose

Ubuntu Docker インストール Documents

2023 年 5 月時点の Documents から必要な内容だけをまとめて記載した。

sudo usermod -aG docker $USER

このコマンドで sudo なしに docker コマンドを使えるようになる。 このコマンドを入れない場合 /var/run/docker.sock: connect: permission denied が発生する。

4. docker-compose.yml の作成

version: '3.8'

networks:
  {希望のネットワーク名}:
    driver: bridge

services:

  # Jenkins Container 設定
  jenkins:
    container_name: jenkins
    image: jenkins/jenkins:lts
    restart: unless-stopped
    ports:
      - "{希望のポート番号}:8080"
    volumes:
      - ./mount/jenkins:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock
    user: root
    environment:
      - TZ=Asia/Seoul
    networks:
      - {希望のネットワーク名}

デプロイパイプラインのツールとして Jenkins を使う前提で書いた。

追加のネットワークが不要なら networks 部分は省略でき、Docker out of Docker を使うためにソケットのボリュームバインディングを行う。必要に応じて Time Zone や追加のボリュームバインディングも設定可能だ。

5. 使うドメインの DNS 設定

ドメイン事業者によって方式が違う。利用しているドメイン事業者の登録方法に従って、自分のサーバ IP を登録してドメインに紐付ける処理が必要だ。

What's My DNS

登録してすぐ使えるわけではなく、DNS サーバへの反映時間が存在する。手軽に伝播状況を確認できるサイトを上に貼っておいた。

6. Let’s Encrypt を使った証明書発行

1) docker-compose.yml
# Nginx 設定
  nginx:
    container_name: nginx
    image: nginx:latest
    restart: unless-stopped
    volumes:
      - ./mount/nginx/nginx.conf:/etc/nginx/nginx.conf 			# Nginx 設定のためのバインディング
      - ./mount/nginx/sites-available:/etc/nginx/sites-available
      - ./data/certbot/conf:/etc/letsencrypt				# 証明書共有のためのバインディング
      - ./data/certbot/www:/var/www/certbot
    ports:
      - 80:80
      - 443:443
    environment:
      - TZ=Asia/Seoul
    networks:
      - {希望のネットワーク名}

  # Certbot コンテナ設定
  certbot:
    container_name: certbot
    image: certbot/certbot
    restart: unless-stopped
    volumes:
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    environment:
      - TZ=Asia/Seoul

この内容を docker-compose.yml に追加し、それ以上の処理は行わない。

2) mount/sites-available/default.conf
server {
     listen 80;
     listen [::]:80;

     server_name my_domain.org;

     location /.well-known/acme-challenge/ {
             allow all;
             root /var/www/certbot;
     }
}

ファイルを作成し、server_name の後ろに自分のドメインを記載した内容を入れる。

3) mount/nginx.conf
user nginx;
worker_processes auto;
worker_priority 0;

pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
        worker_connections 1024;
        multi_accept off;
}

http {

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
        server_tokens off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        access_log /var/log/nginx/access.log main;
        error_log /var/log/nginx/error.log debug;

        gzip on;

        include /etc/nginx/sites-available/*;

}

nginx を初めてインストールするときの内容を基に、適度な初期設定を入れた。

4) Let's Encrypt を通じた手続きでの証明書発行
curl -L https://raw.githubusercontent.com/wmnnd/nginx-certbot/master/init-letsencrypt.sh -o init-letsencrypt.sh
chmod +x init-letsencrypt.sh
vi init-letsencrypt.sh			# ここでドメインとメールアドレスを入力する必要あり
sudo ./init-letsencrypt.sh

証明書発行の手続きを助けてくれるファイルを外部から取得し、権限を設定する。その後、自分のドメインとメールアドレスをファイルに入力し、そのファイルを実行することで証明書発行を進められる。発行に失敗した場合はログを見て原因を見つけ、修正して再試行すれば良い。

7. 証明書更新まで考慮した Nginx 設定

# Nginx 設定
  nginx:
    container_name: nginx
    image: nginx:latest
    restart: unless-stopped
    volumes:
      - ./mount/nginx.conf:/etc/nginx/nginx.conf
      - ./mount/sites-available:/etc/nginx/sites-available
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    ports:
      - 80:80
      - 443:443
    environment:
      - TZ=Asia/Seoul
    networks:
      - {接続したいネットワーク名}
    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"

  # Certbot コンテナ設定
  certbot:
    container_name: certbot
    image: certbot/certbot
    restart: unless-stopped
    volumes:
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    environment:
      - TZ=Asia/Seoul
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

Let's Encrypt で発行した証明書には使用期限があり、代わりに有効期限が近づくと再発行できる。certbot コンテナと nginx コンテナに再発行関連のコマンドを追加することで、証明書更新が自動で進むよう処理できる。証明書発行が完了している状態なので、nginx がエラーでコンテナビルドに失敗することはなくなる。

8. Jenkins コンテナ内に Docker をインストール


追加した内容) もともとはコマンドを直接並べていたが、直感的でないと感じたので修正!

# /jenkins-docker-install.sh

apt-get update
apt-get install ca-certificates curl gnupg

install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null

apt-get update
apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

既存の docker インストール手順から sudo を抜いたコードを sh ファイルにした。このファイルをマウントで内部に入れて実行すれば、すっきりと一発でインストールできる!


9. Docker Compose の処理

version: '3.8'

networks:
  {ネットワーク名}:
    driver: bridge

services:

  # Jenkins Container 設定
  jenkins:
    container_name: jenkins
    image: jenkins/jenkins:lts
    restart: unless-stopped
    ports:
      - "{ポート番号}:8080"
    volumes:
      - ./mount/jenkins:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock
      - ./mount/jenkins-html:/var/lib/jenkins
      - ./jenkins-docker-install.sh:/jenkins-docker-install.sh
    user: root
    environment:
      - TZ=Asia/Seoul
    networks:
      - {ネットワーク名}

# Nginx 設定
  nginx:
    container_name: nginx
    image: nginx:latest
    restart: unless-stopped
    volumes:
      - ./mount/nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./mount/nginx/sites-available:/etc/nginx/sites-available
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    ports:
      - 80:80
      - 443:443
    environment:
      - TZ=Asia/Seoul
    networks:
      - {ネットワーク名}
    depends_on:
      - {先行コンテナ}
    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"

  # Certbot コンテナ設定
  certbot:
    container_name: certbot
    image: certbot/certbot
    restart: unless-stopped
    volumes:
      - ./data/certbot/conf:/etc/letsencrypt
      - ./data/certbot/www:/var/www/certbot
    environment:
      - TZ=Asia/Seoul
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

このように Jenkins 設定まで追加したあと

docker-compose -f {yml ファイルの場所およびファイル名} up -d

コマンドでコンテナを全て立ち上げられる。

docker exec -it jenkins bin/bash
sh /jenkins-docker-install.sh

このように Jenkins コンテナ内部に Docker をインストールすることで、Jenkins 内部の Shell コマンドから Docker Engine を制御できるようになる。 既存の Docker Documents を参考にしたが、違いは sudo を使わない点と、Debian 用 Docker をインストールする点だ。

オプション

1. Swap Memory 設定

# swap memory 設定

sudo fallocate -l {設定容量} /swapfile   # 設定容量はだいたい G 単位で設定する
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

# 再起動後も継続使用したい場合

sudo vi /etc/fstab 			# '/swapfile swap swap defaults 0 0' をこのファイルに挿入

# swap を無効化

sudo vi /etc/fstab 			# '/swapfile swap swap defaults 0 0' を削除
sudo swapoff -v /swapfile
sudo rm /swapfile

インスタンス性能や必要に応じてインスタンスが落ちないように、スワップメモリ設定を行うことができる。

2. Jenkins プラグインインストールエラー時の対処方法

<解決策 1> Jenkins の管理 > Plugin Manager > Advanced settings > 更新サイトのパートにアクセス https://updates.jenkins.io/update-center.jsonhttp://updates.jenkins.io/update-center.json に変更

<解決策 2> skip-certificate-check という名前のプラグインをインストール

完璧な解決策ではないが、これらの対策を全て適用することである程度効果が見られた。

締めくくりに

Docker 関連の内容は、後で扱う一部フレームワークのデプロイを除けば、ここでひと段落させる予定だ。それでも自分が作ったものを実際のネットワーク上で確認できるという達成感があるので、デプロイに関する内容は機会があれば今後ももう少し書いていきたい。

댓글 작성

게시글에 대한 의견을 남겨 주세요.

댓글 0