この記事は ConoHaのカレンダー | Advent Calendar 2023 の7日目の記事です。
VPS上にアプリケーションを簡単にデプロイしたい。
そういう気持ちで何年も色々試行錯誤してきましたので、現状の最適解をまとめて共有しようと思います。
GitHubへプッシュした後、GitHubのGUIからデプロイアクションのボタンをクリックすると、
イメージビルド、レジストリへのプッシュ、サービスの再起動が自動で行われるようになります。
また、GitHub Actionsは実行タイミングを設定できるので、
mainブランチへコミットが発生したタイミングで自動デプロイや、毎日午前0時に自動デプロイなども可能です。
デプロイイメージ
自動デプロイがセットアップされたリポジトリ
アプリケーションを作成する
まず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のリポジトリへプッシュする
作成したリポジトリはGitHubへプッシュしておきます。
2023.adventcalendar.a10a.app % git push origin main
ConoHa VPSサーバーを立ち上げる
ConoHaのダッシュボードからVPSサーバを追加します。
今回は使い捨てるので設定は適当です。
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コンテナを立ち上げる
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コンテナを立ち上げる
DockerレジストリサービスはプライベートなDocker Hubのようなものです。
これに対してdocker push
やdocker 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へアプリケーションイメージをプッシュする
先ほど立てた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"]}
プッシュしたイメージからコンテナサービスを起動する
プッシュしたイメージからコンテナサービスを起動します
[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.a10a.app
に変わっています。
2023.adventcalendar.a10a.app % curl https://adventcalendar.a10a.app
{"message":"hello from adventcalendar.a10a.app"}
GitHub Actionsからデプロイを実行する
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で参照するシークレットを設定します。
設定するシークレットは以下です。
+--------------------------+----------------------------------------+
| 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のボタンをクリックすると、ビルドからデプロイ、再起動まで自動で行われます。
おわり
最初にNginx ProxyやらDocker Registryやらをセットアップするのが若干面倒ですが、
設定さえしてしまえばデプロイ作業がとても楽になります。
冒頭でも書きましたが、GitHub Actionsは実行タイミングを設定できるので、
mainブランチへコミットが発生したタイミングで自動デプロイや、毎日午前0時に自動デプロイなども可能です。
さらにGitHub Actionsで使用するシークレットは検証環境用と本番用に分けることができ、
アクション実行時にどの環境を使用するか選べたりします。
これもまたとても便利なので、調べて使ってみるともしかしたら幸せになれるかもしれません。
それではここまで読んでいただきありがとうございます。お疲れ様でした。