1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ConoHaAdvent Calendar 2023

Day 7

ConoHa VPSでDocker RegistryをセルフホストしてGitHubからコンテナサービスを簡単デプロイする

Last updated at Posted at 2023-12-06

この記事は ConoHaのカレンダー | Advent Calendar 2023 の7日目の記事です。

VPS上にアプリケーションを簡単にデプロイしたい。
そういう気持ちで何年も色々試行錯誤してきましたので、現状の最適解をまとめて共有しようと思います。

GitHubへプッシュした後、GitHubのGUIからデプロイアクションのボタンをクリックすると、
イメージビルド、レジストリへのプッシュ、サービスの再起動が自動で行われるようになります。

また、GitHub Actionsは実行タイミングを設定できるので、
mainブランチへコミットが発生したタイミングで自動デプロイや、毎日午前0時に自動デプロイなども可能です。

デプロイイメージ

adventcalendar.2023.9.png

自動デプロイがセットアップされたリポジトリ

アプリケーションを作成する

adventcalendar.2023.1.png

まずVPSで動かしたいアプリケーションを作成します。
今回はホスト名を返却するだけの簡単なWebサーバを作成しました。

main.go
package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.ListenAndServe(":3000", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")

		fmt.Fprintf(w, "{\"message\":\"hello from %s\"}\n", r.Host)
	}))
}
2023.adventcalendar.a10a.app % curl http://localhost:3000 -v
*   Trying 127.0.0.1:3000...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Date: Sat, 02 Dec 2023 10:54:44 GMT
< Content-Length: 40
<
{"message":"hello from localhost:3000"}
* Connection #0 to host localhost left intact

GitHubのリポジトリへプッシュする

adventcalendar.2023.2.png

作成したリポジトリはGitHubへプッシュしておきます。

2023.adventcalendar.a10a.app % git push origin main

ConoHa VPSサーバーを立ち上げる

adventcalendar.2023.3.png

ConoHaのダッシュボードからVPSサーバを追加します。
今回は使い捨てるので設定は適当です。

スクリーンショット 2023-12-02 21.17.11.png

スクリーンショット 2023-12-04 8.43.41.png

スクリーンショット 2023-12-04 8.44.02.png

VPSサーバが起動したらSSHでログインします。

2023.adventcalendar.a10a.app % ssh root@133.130.91.35

VPSにDockerをインストールします。

[root@133-130-91-35 ~]# yum install \
>	device-mapper-persistent-data \
>	lvm2 \
>	yum-utils
[root@133-130-91-35 ~]# yum-config-manager \
>	--add-repo \
>	https://download.docker.com/linux/centos/docker-ce.repo
[root@133-130-91-35 ~]# yum install docker-ce
[root@133-130-91-35 ~]# systemctl start docker

Nginx Proxyコンテナを立ち上げる

adventcalendar.2023.4.png

Nginx ProxyがVPSサーバへのHTTPリクエストを適切なコンテナサービスへルーティングします。

今回の場合、registry.a10a.appのホスト名で来たリクエストはDockerレジストリのサービスへ、adventcalendar.a10a.appのホスト名で来たリクエストは最初に作成したWebサーバのサービスへ流れるようにしていきます。

今回使用しているa10a.appのドメインはcloudflareで管理しており、cloudflareがHTTPSのリクエストをいい感じにしてくれています。
そのためVPSサーバではHTTPSの設定は行いませんが、必要であればnginx-proxy/acme-companionを使用して簡単にHTTPSに対応することができます。

[root@133-130-91-35 ~]# mkdir nginxproxy
[root@133-130-91-35 ~]# cd nginxproxy
[root@133-130-91-35 nginxproxy]# mkdir certs htpasswd vhost.d
[root@133-130-91-35 nginxproxy]# vim compose.yaml
compose.yaml
services:
  nginx-proxy:
    image: nginxproxy/nginx-proxy
    container_name: nginxproxy-nginx-proxy
    volumes:
      - ./certs:/etc/nginx/certs
      - ./htpasswd:/etc/nginx/htpasswd
      - ./vhost.d:/etc/nginx/vhost.d
      - /var/run/docker.sock:/tmp/docker.sock:ro
    ports:
      - 80:80
      - 443:443
    networks:
      nginxproxy-network:

networks:
  nginxproxy-network:
    name: "nginxproxy-network"
[root@133-130-91-35 nginxproxy]# docker compose up -d
[root@133-130-91-35 nginxproxy]# docker compose ps
NAME                     IMAGE                    COMMAND                                       SERVICE       CREATED              STATUS              PORTS
nginxproxy-nginx-proxy   nginxproxy/nginx-proxy   "/app/docker-entrypoint.sh forego start -r"   nginx-proxy   About a minute ago   Up About a minute   0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp

Docker Registryコンテナを立ち上げる

adventcalendar.2023.5.png

DockerレジストリサービスはプライベートなDocker Hubのようなものです。
これに対してdocker pushdocker pullを行うことができます。

Docker HubやGitHub Packagesを使用することもできますが、制約が多いので自分用のレジストリを立ててしまった方が気が楽です。

とはいえ認証が全く行われない場合、攻撃を受ける可能性が大きくなるので、一応Basic認証を付けておきます。

compose.yamlにて指定している環境変数VIRTUAL_HOST, VIRTUAL_PORTは、Nginx Proxyが使用するためのものです。

[root@133-130-91-35 ~]# mkdir registry.a10a.app
[root@133-130-91-35 ~]# cd registry.a10a.app
[root@133-130-91-35 registry.a10a.app]# mkdir registry
[root@133-130-91-35 registry.a10a.app]# vim compose.yaml
compose.yaml
services:
  app:
    image: registry:2
    container_name: registry-a10a-app
    environment:
      VIRTUAL_HOST: registry.a10a.app
      VIRTUAL_PORT: 5000
    volumes:
      - ./registry:/var/lib/registry
    networks:
      nginxproxy-network:

networks:
  nginxproxy-network:
    name: "nginxproxy-network"
    external: true
[root@133-130-91-35 registry.a10a.app]# docker compose up -d
[root@133-130-91-35 registry.a10a.app]# docker compose ps
NAME                IMAGE        COMMAND                                            SERVICE   CREATED         STATUS         PORTS
registry-a10a-app   registry:2   "/entrypoint.sh /etc/docker/registry/config.yml"   app       4 seconds ago   Up 3 seconds   5000/tcp

Basic認証を設定します。

[root@133-130-91-35 registry.a10a.app]# cd ~/nginxproxy/
[root@133-130-91-35 nginxproxy]# cd htpasswd/
[root@133-130-91-35 htpasswd]# yum install httpd-tools
[root@133-130-91-35 htpasswd]# htpasswd -c registry.a10a.app a10a
New password:
Re-type new password:
Adding password for user a10a
[root@133-130-91-35 htpasswd]# cat registry.a10a.app
a10a:$apr1$u5KO1fT0$vHYnN4aENEhEeN1qVKN8U.

docker pushでプッシュするDockerイメージは結構サイズが大きいので、413 Payload Too Largeエラーを避けるために設定を追加します。

[root@133-130-91-35 htpasswd]# cd ../vhost.d/
[root@133-130-91-35 vhost.d]# echo "client_max_body_size 512m;" >> registry.a10a.app
[root@133-130-91-35 vhost.d]# cat registry.a10a.app
client_max_body_size 512m;

Nginx Proxyを再起動します。

[root@133-130-91-35 vhost.d]# cd ..
[root@133-130-91-35 nginxproxy]# docker compose restart

ローカルからDockerレジストリへリクエストを送信して、Basic認証が効いているか確認します。

2023.adventcalendar.a10a.app % curl https://registry.a10a.app/v2/_catalog
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.25.2</center>
</body>
</html>
2023.adventcalendar.a10a.app % echo -n a10a:password | base64 | xargs -I {} curl -H "Authorization: Basic {}" https://registry.a10a.app/v2/_catalog
{"repositories":[]}

Docker Registryへアプリケーションイメージをプッシュする

adventcalendar.2023.6.png

先ほど立てたDockerレジストリへWebサーバのDockerイメージをプッシュします。

2023.adventcalendar.a10a.app % docker build -t registry.a10a.app/2023.adventcalendar.a10a.app:latest . 
2023.adventcalendar.a10a.app % docker login registry.a10a.app
Username: a10a
Password:
Login Succeeded
2023.adventcalendar.a10a.app % docker push registry.arakaki.app/2023.adventcalendar.a10a.app:latest
2023.adventcalendar.a10a.app % echo -n a10a:password | base64 | xargs -I {} curl -H "Authorization: Basic {}" https://registry.a10a.app/v2/_catalog
{"repositories":["2023.adventcalendar.a10a.app"]}

プッシュしたイメージからコンテナサービスを起動する

adventcalendar.2023.7.png

プッシュしたイメージからコンテナサービスを起動します

[root@133-130-91-35 nginxproxy]# cd ~/
[root@133-130-91-35 ~]# mkdir adventcalendar.a10a.app
[root@133-130-91-35 ~]# cd adventcalendar.a10a.app
[root@133-130-91-35 adventcalendar.a10a.app]# vim compose.yaml
compose.yaml
services:
  app:
    image: registry.a10a.app/2023.adventcalendar.a10a.app:latest
    container_name: adventcalendar-a10a-app
    environment:
      VIRTUAL_HOST: adventcalendar.a10a.app
      VIRTUAL_PORT: 3000
    networks:
      nginxproxy-network:

networks:
  nginxproxy-network:
    name: "nginxproxy-network"
    external: true
[root@133-130-91-35 adventcalendar.a10a.app]# docker login registry.a10a.app
[root@133-130-91-35 adventcalendar.a10a.app]# docker compose up -d
[root@133-130-91-35 adventcalendar.a10a.app]# docker compose ps
NAME                           IMAGE                                                      COMMAND   SERVICE   CREATED          STATUS          PORTS
adventcalendar-a10a-app   registry.a10a.app/2023.adventcalendar.a10a.app:latest   "/app"    app       31 seconds ago   Up 30 seconds   3000/tcp

クライアントからコンテナサービスを利用する

adventcalendar.2023.8.png

クライアントから接続確認を行います。
レスポンスのホスト名がadventcalendar.a10a.appに変わっています。

2023.adventcalendar.a10a.app % curl https://adventcalendar.a10a.app
{"message":"hello from adventcalendar.a10a.app"}

GitHub Actionsからデプロイを実行する

adventcalendar.2023.9.png

VPSサーバでSSHキーを作成しておきます。

[root@133-130-91-35 ~]# cd .ssh
[root@133-130-91-35 .ssh]# ssh-keygen -t ed25519
[root@133-130-91-35 .ssh]# cat id_ed25519.pub >> authorized_keys

GitHub Actionsの設定は.github/workflows/deploy.ymlへ書いていきます。

deploy.yml
name: Deploy

on:
  workflow_dispatch:

permissions: write-all

jobs:
  main:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false

      - name: docker login
        run: |
          echo "${{ secrets.DOCKER_REGISTRY_PASSWORD }}" \
           | docker login \
              --username ${{ secrets.DOCKER_REGISTRY_USERNAME }} \
              --password-stdin \
              ${{ secrets.DOCKER_REGISTRY_HOST }}

      - name: docker build
        run: |
          docker build \
              --tag ${{ secrets.DOCKER_REGISTRY_HOST }}/2023.adventcalendar.a10a.app:latest \
              .

      - name: docker push
        run: docker push ${{ secrets.DOCKER_REGISTRY_HOST }}/2023.adventcalendar.a10a.app:latest

      - name: setup ssh
        run: |
          mkdir -p ~/.ssh
          ssh-keyscan ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
          echo "${{ secrets.SSH_KEY }}" > ~/.ssh/id_ed25519
          chmod 700 ~/.ssh/id_ed25519

      - name: ssh deploy
        run: |
          ssh ${{ secrets.SSH_USER}}@${{ secrets.SSH_HOST }} ' \
              cd ~/2023.adventcalendar.a10a.app \
               && docker compose pull \
               && docker compose build \
               && docker compose up -d \
          '

deploy.ymlで参照するシークレットを設定します。

スクリーンショット 2023-12-06 14.44.19.png

設定するシークレットは以下です。

+--------------------------+----------------------------------------+
| Name                     | Secret                                 | 
+--------------------------+----------------------------------------+
| DOCKER_REGISTRY_HOST     | registry.a10a.app                      | Docker Registryのホスト名
| DOCKER_REGISTRY_PASSWORD | password                               | Docker RegistryのBasic認証パスワード
| DOCKER_REGISTRY_USERNAME | a10a                                   | Docker RegistryのBasic認証ユーザー名
| SSH_HOST                 | 133.130.91.35                          | VPSサーバーIPアドレス
| SSH_KEY                  | -----BEGIN OPENSSH PRIVATE KEY-----... | SSHキー(~/.ssh/id_ed25519)の値
| SSH_USER                 | root                                   | SSHログインユーザー名
+--------------------------+----------------------------------------+

あとはGitHubのGUIからRun workflowのボタンをクリックすると、ビルドからデプロイ、再起動まで自動で行われます。

スクリーンショット 2023-12-06 15.03.20.png

おわり

最初にNginx ProxyやらDocker Registryやらをセットアップするのが若干面倒ですが、
設定さえしてしまえばデプロイ作業がとても楽になります。

冒頭でも書きましたが、GitHub Actionsは実行タイミングを設定できるので、
mainブランチへコミットが発生したタイミングで自動デプロイや、毎日午前0時に自動デプロイなども可能です。

さらにGitHub Actionsで使用するシークレットは検証環境用と本番用に分けることができ、
アクション実行時にどの環境を使用するか選べたりします。

スクリーンショット 2023-12-06 16.12.56.png

これもまたとても便利なので、調べて使ってみるともしかしたら幸せになれるかもしれません。

それではここまで読んでいただきありがとうございます。お疲れ様でした。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?