Kamal を使用した Web サービス（PostgreSQL を含む）のデプロイ方法

DHH の会社は Kamal を活用してすべての製品をクラウドサービスから移行し、[年間 200 万ドルを節約しています](https://world.hey.com/dhh/our-cloud-exit-savings-will-now-top-ten-million-over-five-years-c7d9b5bd)。これにより、私は Kamal に興味を持ちました。

初めて触れてみて、印象は良好です：

1. Docker 上に構築されているため、コンテナ技術のすべての利点を享受できます。
2. 理解と使用が比較的簡単で、学習コストがほとんどかかりません（ただし、ドキュメントはまだ簡素なため、予期しない問題が発生した場合はネット上で回答を探す必要がありますが、インターネット上の関連コンテンツが増え、公式チームがドキュメントを改善するにつれて、これは問題にならないでしょう）。
3. ゼロダウンタイムデプロイ、ローリングリスタート、リソースブリッジング、リモートビルド、補助サービス管理など、Docker を使用した本番環境での Web アプリケーションのデプロイと管理に必要なすべての機能を提供します。

この記事は Kamal ドキュメントの補足となるものです。

つまり、ドキュメントで簡単に触れられている設定を実践的な方法で再解釈し、同時にドキュメントには記載されていないが実際のサービスデプロイで知っておく必要がある多くの知識も説明しています。
また、プログラム、設定、コマンドの背後にある実装原理も説明しています。原理を理解してこそ間違いを防ぎ、それを魔法のようなものとして扱わず、問題が発生した場合にどこに問題があるのかを把握できるからです。

---

## 前提条件：

1. ローカルで実行できる Web プロジェクト（PostgreSQL データベースを含む）
2. コマンドラインから ssh コマンドでログインできるサーバー（パスワードまたは鍵ファイルのいずれか）
3. [Kamal](https://kamal-deploy.org) がインストールされ、ドキュメントに従って初期化済み（`kamal init`）
4. Dockerhub でアクセストークンを作成済み

`kamal init` はプロジェクトディレクトリにいくつかのファイルを生成しますが、主に使用するのは以下の 2 つです：

1. `.kamal/secrets`
2. `config/deploy.yml`

もちろん、Kamal のドキュメントにはこれらの説明がありますが、非常に簡素で、多くの設定の説明は一言で済まされています。著者はユーザーがサービスデプロイ、Docker、データベース、ネットワークについて既に理解していることを前提としているようです。そのため、予期しない問題に遭遇すると非常に困ります。私はネット上での情報検索と繰り返しのテストに多くの時間を費やしました。

それでは設定について説明していきます。

## .kamal/secrets：

[原ドキュメント](https://kamal-deploy.org/docs/configuration/environment-variables/)

```yml
# 作成した Dockerhub のアクセストークン。Kamal はローカルイメージをビルドした後、
# このキーとユーザー名を利用して指定した Dockerhub リポジトリにアップロードします。
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD

# XAI の API キー
XAI_API_KEY=$XAI_API_KEY
```

これはプロジェクトで使用する環境変数を保存する場所です。

各行は変数のマッピングを示しています。例えば、`KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD` の意味は、Kamal が現在のシステム環境変数から `KAMAL_REGISTRY_PASSWORD` という変数を探し、その値を左側の `KAMAL_REGISTRY_PASSWORD` 変数に割り当てるということです。これらの変数は `config/deploy.yml` ファイル内で使用されます。

環境変数からデータを取得する以外にも、Linux コマンドを利用して他の場所からデータを取得したり、パスワードマネージャーから取得したりすることもできます。この部分の使用方法についてはドキュメントを参照してください。

その他の注意点：

1. このファイルには秘密鍵などの機密環境変数の設定のみを保存するのが最適です。もちろん `TEST_MODE=123` のような平文の環境変数もここに置くことができますが、設計意図とは異なり、直感に反することはしないでください。このような環境変数は `config/deploy.yml` の `env.clear` の下に配置すべきです。
2. このファイルは Github にアップロードしても問題ありません。なぜなら機密情報の平文は含まれていないからです。

## config/deploy.yml：

これが最も重要な設定ファイルです。

```yaml
# サービス名。サービスを識別するために使用します。Kamal は1台のマシンに複数のプログラムをデプロイできるため、
# 識別子が必要です。また、このサービス名はデータベース Docker サービスの命名にも使用されます。
service: test

# Dockerhub のリポジトリ名と一致する必要があります。
# 注意：イメージを非公開にしたい場合は、Dockerhub で手動でプライベートに設定する必要がありますが、
# いずれにしても Kamal のデプロイには影響しません。
image: shuirong/test

# Kamal 使用時に毎回サーバーのログインパスワードを手動入力したくない場合は、
# これらの設定行をコメント解除してください。
# こうすることで、Kamal がサーバーを操作する必要がある場合、ここに指定した秘密鍵を自動的に読み取ります
# （必ずしも pem 拡張子である必要はなく、有効な秘密鍵ファイルであれば問題ありません）
# ssh:
#   keys:
#     - ~/.ssh/xxxx.pem

# デプロイしたいプログラムの設定。一般的には Web サービスをデプロイします
servers:
  # ここではデフォルト名称「web」を使用できます
  web:
    # デプロイしたいサーバーの IPV4 アドレス
    - 185.194.123.12

# プログラムの前にゲートウェイプログラムがあります。ここではゲートウェイに関する設定を行います。
# Kamal はゲートウェイとして Traefik を使用しています。
proxy:
  # Let's Encrypt を使用して SSL 証明書を自動発行し、https アクセスを実現します。
  # 複数の Web サーバーを使用する場合は、この部分を削除し、
  # ロードバランサーで SSL を終端するようにしてください。
  # 注意：ドメインが Cloudflare でホスティングされている、または DNS が Cloudflare でホスティングされている場合、
  # ドメインの DNS 設定で Proxy Status の値が「DNS Only」であることを確認してください。
  # これにより「リダイレクトが多すぎます」という問題を回避できます。両側の SSL サービスは1つだけ有効にする必要があるためです。
  # こちら側の ssl をオフにして、Cloudflare 側で Proxied を選択することも可能です。
  ssl: true
  # プロジェクトのドメイン。1台のマシンにデプロイされる Web サービスのホストは同じにできません。
  # 将来、1台のサーバーに複数の Web サービスをデプロイする場合、ゲートウェイはユーザーがどのドメインを通じてアクセスしたかに基づいて、
  # リクエストをどの Web プログラムにプロキシするかを決定する必要があります。
  host: iyaku.ai
  # プログラムが公開するポート。設定しない場合、デフォルト値は 80 です
  app_port: 8080
  # ssl を true に設定した場合、forward_headers: true を明示的に設定しない限り、
  # ゲートウェイはリクエストヘッダーをアプリケーションに転送しません。
  # この設定は通常必要ありませんが、X-Forwarded-For リクエストヘッダーを解析してユーザーの IP を確認したいときに、
  # このヘッダーが取得できない場合に役立ちます。
  # デフォルトでオンにしておくことをお勧めします。
  forward_headers: true

# Dockerhub 関連の設定
# もちろん、他のイメージホスティングサービスを選択することもできます。その場合はドキュメントを参照してください。
# ここでは Dockerhub についてのみ説明します。
registry:
  # Dockerhub アカウントのユーザー名
  username: xxxx

  # 前述した Dockerhub アカウントのアクセストークンで、イメージのアップロード時に使用されます。
  password:
    - KAMAL_REGISTRY_PASSWORD

# Kamal はローカルでイメージをビルドするため、事前に Docker Desktop ソフトウェアを起動しておく必要があります。
builder:
  # サーバーのアーキテクチャを指定し、Docker が適切なアーキテクチャ向けのイメージをビルドできるようにします。
  # 一般的な Linux サーバーはほとんど amd アーキテクチャなので、デフォルト値で問題ありません。
  # 他のアーキテクチャの場合は、この値を調整してください。
  # Apple M シリーズの arm アーキテクチャのコンピュータを使用している場合、
  # amd アーキテクチャのイメージをビルドする際に奇妙な問題が発生する可能性がありますが、
  # 紙面の都合上ここでは詳しく説明しません。
  arch: amd64

# 上記の Web サービスで使用されるすべての環境変数をここで宣言します。
# 宣言されたものだけが Kamal によって Web サービスのコンテナ環境変数に注入されます。
# `.kamal/secrets` ファイルに書くだけでは不十分です。
env:
  # プロジェクトで使用される非機密情報の環境変数はすべてここに記述できます（ソースコードに直接書きたくない場合）。
  # ここで変数を宣言する必要があるかどうかは、最終的にはプログラムが必要とするかどうかによります。
  # プログラムが外部の環境変数から何も読み取る必要がない場合、
  # ここは完全に空で、何も設定する必要はありません。
  # したがって、あなたの環境変数は私のものとは異なる可能性があります。
  clear:
    # PHX_HOST を 0.0.0.0 に設定することで、コンテナ外部から Phoenix アプリケーションにアクセスできるようになります。
    PHX_HOST: 0.0.0.0
  secret:
    - XAI_API_KEY
    - DATABASE_URL

# Kamal にエイリアスを追加します。つまり、長いコマンドの短縮コマンドです。
aliases:
  # web サービスの Docker コンテナのコマンドラインに直接アクセスするコマンド
  shell: app exec --interactive --reuse "bash"
  # web サービスと下記のデータベースサービスのログを表示するコマンド
  logs: app logs

# この用語はアクセサリーを意味し、Web サービスの補助サービスであり、
# サーバー外部から直接アクセスする必要のないサービスです。
accessories:
  # ここは Kamal 内でのサービス名で、私も postgres としました。もちろん別の名前も使えます。
  # ここで「Kamal 内での名前」と強調したのは、サーバー上で `docker ps` を実行してどのプログラムが
  # 実行されているかを確認すると、データベースの Docker コンテナ名がこれではなく、
  # 冒頭の <service name> + postgres、例えば test-postgres になっているからです。
  # test-postgres は Docker コンテナサービスの名前であり、これは非常に重要です。
  # Web サーバーからデータベースサービスに接続する場合、この名前を通じて接続するためです（postgres ではなく）。
  postgres:
    # インストールするデータベースイメージのバージョン
    image: postgres:15
    # データベースをインストールするサーバーアドレス。データベースを Web サービスと同じマシンにデプロイしたい場合は、
    # 同じアドレスを入力します。
    # この設定は最初、私を戸惑わせました。潜在意識では同じマシンにデプロイするのは当然と思っていましたが、
    # なぜ追加設定が必要なのかと疑問に思いました。
    host: 185.194.123.12
    # postgres の Docker コンテナがマッピングするポート
    port: '127.0.0.1:5432:5432'
    # postgres に必要な環境変数情報。他のデータベースでは要件が異なる場合があるので、
    # 対応するデータベースのドキュメントを参照してください。
    env:
      clear:
        POSTGRES_USER: 'postgres'
        POSTGRES_DB: 'production_db'
      secret:
        # 前述の env 設定と同様に、ここで使用する前提は `.kamal/secrets` で変数が既に宣言されていることです。
        - POSTGRES_PASSWORD
    # postgres コンテナ内のデータファイルのマッピング。これをホストマシンにマッピングする必要があります。
    # そうしないと、コンテナが再起動した場合にデータがすべて失われます。
    # 他のデータベースコンテナのデータパスは同じではない可能性があるので、対応するドキュメントを確認してください。
    directories:
      - data:/var/lib/postgresql/data
```

最後に、Kamal のデプロイについて、ローカルからビルドせずに Dockerhub 上のイメージを直接デプロイしたい場合は、この [issue](https://github.com/basecamp/kamal/discussions/1132#discussioncomment-13022584) を参照してください。

>　本記事は私の中国語ブログから翻訳されたものです：https://shuirong.iyaku.ai