リモート開発メインのソフトウェア開発企業のエンジニアブログです

Azure App Service + Docker で Rails アプリを動かす

概要

会社でも個人でも AWS を使う事が多いのですが、今回、個人的なサイト作成で、勉強も兼ねて Azure App Service を使って Rails のアプリを作成してみました。

構成は以下の通りシンプルなものです。

  • 本番環境
    • Azure App Service
      • Custom Docker image
    • Azure Database for PostgreSQL
    • Azure Container Registry に、本番用コンテナを保存
  • ローカル開発環境
    • Docker Compose
    • コンテナは Web (Rails), DB, Webpacker の3つ
  • その他
    • Terraform で本番環境を構築
    • Rails 6.0.2

同じような構成の情報がそこまで多くなかったので、本記事を書くことにしました。参考になれば幸いです。

その他の選択肢

今回の構成に関する説明の前に、その他にどのような選択肢があるかについて簡単に記載します。本構成の設定方法などをすぐに知りたい方は、飛ばしてもらって構いません。

Ruby のランタイムを使用する

今回は、Dockerfile を使って Docker イメージを作成し、それを Azure App Service で動かしています。Ruby アプリを動かす別の方法として、Ruby のランタイムを使用して、ソースを Azure 上に push するという方法もあります。Heroku みたいなものと思ってもらえれば良いと思います。以下の Microsoft のページを読めば、概略が分かります。

Quickstart: Create a Linux Ruby app – Azure App Service | Microsoft Docs

今回、Ruby ランタイムを使用しなかった理由としては、Docker Compose を使ってローカル開発環境を構築していたので、Dockerfile をそのまま使おうと思ったというのがあります。

本番でも Docker Compose を使用する

今回は、Dockerfile で定義した単一のコンテナを Azure App Service にデプロイしていますが、(2020/5/10現在 preview 機能ですが)Docker Compose を使って複数コンテナをデプロイすることも出来ます。そうすれば、ローカルと本番を全く同じ構成にすることが出来ます。

ただ、今回の構成は Web + DB という単純なもので、DB には Azure のマネージドサービスである Azure Database for PostgreSQL を使用したかったため、単一のコンテナをデプロイする方法を選びました。

Multi-container のデプロイ方法は、以下のドキュメントを参照して下さい。

Quickstart: Create a multi-container app – Azure App Service | Microsoft Docs

Resource Manager を使って環境構築する

今回はインフラ構築に使い慣れた Terraform を使いましたが、Azure では Resource Manager というものでインフラ構築が出来るようです。

ただ、以下の2つの理由で、今回は Terraform を使う事にしました。

1つ目は、ちょっと検索したところ、以下のような記事を見つけたことです。

Azure Resource Manager — ARM uses json format. ARM files tend to be quite verbose and therefore long.

https://medium.com/@kari.marttila/how-to-create-infrastructure-as-code-for-aws-and-azure-ab0a5ddecc06

2つ目は、今回、本格的に Azure App Service を使うのが初めてだったため、複数の新しい技術を同時に覚えたくなかったことです。

インフラの構築

構成

Azure App Service でアプリを構築する場合、まずは App Service plan というのを作成し、その配下に App Service を作成することになります。

また、今回は全てのリソースを同一のリソースグループに入れましたので、構成としては以下のような形になります。

  • リソースグループ
    • App Service plan
      • App Service (Webアプリ)
    • Azure Database for PostgreSQL (DB)
    • Azure Container Registry (ビルドした Docker コンテナを保存)

なお、今回は、VNet は使用していません。VNet を使おうとすると、Azure Database for PostgreSQL や App Service plan で高いプランにする必要があるというのが主な理由です。

Terraform

App Service の設定項目は沢山あり、個人的には把握するまでに時間がかかったので、設定内容を全部載せておきます。また、分かりにくい点は、コード中にコメントを入れておきます。

resource "azurerm_app_service_plan" "main" {
  name                = "${var.app_name}-app-service-plan"
  # リソースグループの定義は省略
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  # Windows か Linux かは、App Service plan 毎に決める
  kind                = "Linux"
  reserved            = true

  # See: https://azure.microsoft.com/en-us/pricing/details/app-service/linux/
  sku {
    tier = "Basic"
    size = "B1"
  }
}

resource "azurerm_app_service" "web" {
  name                = "${var.app_name}-app-service"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  app_service_plan_id = azurerm_app_service_plan.main.id

  site_config {
    # 
    linux_fx_version  = "DOCKER|foo.azurecr.io/foo:latest"
    always_on        = "true"
  }

  app_settings = {
    # https://github.com/Azure/azure-rest-api-specs/issues/1698
    WEBSITES_ENABLE_APP_SERVICE_STORAGE = false

    # ここが間違っていると、イメージを Docker Hub からダウンロードしようとするので要注意
    DOCKER_REGISTRY_SERVER_URL      = "https://foo.azurecr.io"
    DOCKER_REGISTRY_SERVER_USERNAME = "foo"
    DOCKER_REGISTRY_SERVER_PASSWORD = var.acr_password

    WEBSITES_PORT     = 3000

    DATABASE_HOST     = azurerm_postgresql_server.main.fqdn
    DATABASE_PORT     = 5432
    DATABASE_USER     = "${azurerm_postgresql_server.main.administrator_login}@${azurerm_postgresql_server.main.fqdn}"
    DATABASE_PASSWORD = var.postgresql_administrator_password

    RAILS_MASTER_KEY  = var.rails_master_key
    RAILS_ENV         = var.rails_env
    # nginx などは立てず、 Rails で静的ファイルも返すようにする
    RAILS_SERVE_STATIC_FILES = true

    SENDGRID_API_KEY  = var.sendgrid_api_key
  }

  connection_string {
    name  = "Database"
    type  = "PostgreSQL"
    # 一応設定してみたが、どこで使われているのか不明。設定は不要かも。
    value = "host=${azurerm_postgresql_server.main.fqdn}; dbname=${azurerm_postgresql_database.foo.name} user=postgres@${azurerm_postgresql_server.main.fqdn} password=${var.postgresql_webuser_password} port=5432 sslmode=require"
  }
}

コンテナの設定

参考となる情報

Rails アプリを Docker で動かす方法は、Docker 公式サイトの以下のサンプルが(Docker Compose を使って DB も一緒に立ち上げていますが)参考になると思います。

Quickstart: Compose and Rails | Docker Documentation

また、カスタムの Docker イメージで App Service を立ち上げる方法は、Django の例ではありますが、以下のチュートリアルが参考になると思います。

Tutorial: Build and run a custom image – Azure App Service | Microsoft Docs

Dockerfile

参考情報の内容を理解している前提で、Dockerfile の内容を説明します。まずは、Dockerfile をそのまま載せます。(実際に使っているものと少し異なりますが。)

FROM ruby:2.7
RUN apt-get update -qq && apt-get install -y postgresql-client vim && \
    apt-get install -y apt-transport-https && \
    curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update -qq && apt-get install -y yarn && \
    curl -sL https://deb.nodesource.com/setup_12.x | bash - && \
    apt-get install -y nodejs

# Install SSH server
# https://docs.microsoft.com/en-us/azure/app-service/containers/tutorial-custom-docker-image#enable-ssh-connections
ENV SSH_PASSWD "root:Docker!"
RUN apt-get update \
        && apt-get install -y --no-install-recommends dialog \
        && apt-get update \
  && apt-get install -y --no-install-recommends openssh-server \
  && echo "$SSH_PASSWD" | chpasswd
COPY sshd_config /etc/ssh/

RUN mkdir /myapp
WORKDIR /myapp

COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
COPY yarn.lock /myapp/yarn.lock

RUN bundle install
RUN yarn install

COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000 2222

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]

重要なポイントとしては2つです。

  • SSH サーバーを 2222 番ポートで、立ち上げる
  • root のパスワードを Docker! に設定する

ポートは必ず 2222 で、パスワードも必ず Docker! にしないと、コンテナ内に SSH で入ることが出来ず、トラブルシューティングが困難になります。

entrypoint.sh

entrypoint.sh は、Docker 公式サイトのサンプルのものにいくつかの行を付け加えて使用しています。

#!/bin/bash
set -e

# SSH サーバーがポート 2222 で立ち上がる
service ssh start

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# 起動時にマイグレーションを実行する
if [[ $RAILS_ENV = 'production' ]]; then
  rails db:migrate
fi

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

デプロイ

デプロイの手順は以下の通りです。

  1. Azure Container Registry にログインする(初回のみ)
  2. Docker コンテナをビルドする
  3. Azure Container Registry に push する
  4. App Service を再起動する(その際に、最新のコンテナを pull してくる)

コマンドとしては以下の通りです。

docker login foo.azurecr.io
docker build -t foo.azurecr.io/foo .
docker push foo.azurecr.io/foo:latest

その後、Azure Portal 上で、アプリの再起動を行います。

トラブルシューティング

Azure App Service でのトラブルシューティングの基本は、コンテナに SSH で入って、ログファイルなどを確認することです。そのためにも、前述の通り Dockerfile 内で sshd を起動しておくことが重要です。

Kudu (Advanced Tools)

コンテナへの SSH での接続は、Azure Portal から行うのが簡単です。左側のメニューで下の方に行き、SSH を選べば良いのですが、その1つ下の「Advanced Tools」から説明します。

Advance Tools から Go のリンクで開くと、以下のような画面が出てきます。

Kudu

ここで動いているのは Kudu というみたいです。

上の方のリンクに「Bash」と「SSH」がありますが、

  • Bash: Docker コンテナが動いているホスト OS の bash に入る
  • SSH: Docker コンテナ内で動いている sshd に接続する

という違いがあります。「Bash」の方で、Ruby のプロセスや Rails のログを確認しようとしても出来ないので要注意です。

また、AWS の Elastic Beanstalk であれば、ホストは単なる SSH ですので、 eb ssh コマンドなどでホスト OS にログインした上で、 docker exec などでコンテナ内に入れるのですが、Azure App Service の場合、上述の「Bash」でホストに入ったとしても、そもそも docker コマンドがありませんし、コンテナに入ることは出来ません。

SSH

Kudu の画面上部にある「SSH」リンクをクリックすると、ブラウザ上でコンテナ内の SSH サーバーに接続出来ます。SSH で接続した後は、特別なことはないので、ログファイルを確認したりしてください。

なお、Rails のプロセスはプロセスID 1で起動していますので、コンテナ内から再起動は出来ません。

Bash

一度コンテナが起動してしまえば「Bash」で出来ることは大してないのですが、コンテナが起動しない場合などに /home/LogFiles 配下のログで原因を調べることが出来ます。

まとめ

Azure App Service で Ruby on Rails の web アプリを動かす方法は、Ruby ランタイムを使う方法もありますが、今回はカスタムの Docker イメージを使う方法を選択しました。Docker コンテナをビルドして Azure Container Registry に push し、それを起動するという方法です。

一度分かってしまえばそこまで難しくないのですが、細かい部分まで解説したドキュメントがなかったり、似たような構成で動かしている人が少なかったため、それなりに苦労しました。

Azure は、業務システム系の案件で何度か触ったことがあり、メリットもある程度は分かっていますが、ウェブアプリに関しては AWS に比べると細かいところに手が届かないと思う点がいくつかありました。今後の改善に期待します。

← 前の投稿

Scala + Kinesis Client LibraryでKinesisコンシューマーアプリケーションを作る

次の投稿 →

Vagrant環境でRailsのリモートデバッグ(ブレークポイント使用可能)

コメントを残す