ishikawa_pro's memorandum

若手webエンジニアの備忘録です.

Swift Node.js Docker AWS etc...色々やります。

Heroku Container Registryを使ってみた

こんにちは。
Heroku Container Registryを早速使ってみたので、まとめます。普通に、デプロイするだけだと他の人がたくさんやっていて面白くないので、ローカルの開発環境の構築からDeployするところまでやってみます。

今回の構成

Ruby on Rails のサンプルアプリを作ります。ローカルの開発環境はDocker上にRuby on Rails のコンテナとPostgreSQLのコンテナを立てて、docker-composeで管理します。dockerのimageは全部Docker Hubにある公式のリポジトリを使います。本番環境は、Heroku Container Registory と Heroku Postgresを使います。

ディレクトリ構成

HerokuSampleApp
├── .dockerignore
├── .env
├── Dockerfile
├── HerokuSampleApp
│   ├── .gitignore
│   ├── Gemfile
│   ├── Gemfile.lock
│   ├── README.md
│   ├── Rakefile
│   ├── app
│   ├── bin
│   ├── config
│   ├── config.ru
│   ├── db
│   ├── lib
│   ├── log
│   ├── public
│   ├── tmp
│   └── vendor
└── docker-compose.yml

手元の開発環境を構築

まずは、ローカルのDocker環境で動かせるように整備していきます。

Rails プロジェクトを作成

docker コマンドを使ってrailsのプロジェクトを生成します。

docker run -it --rm -w /HerokuSampleApp/ -v $(pwd)/:/HerokuSampleApp/ rails rails new HerokuSampleApp -TB --database=postgresql

ちょっと解説すると、-wオプション(--workdir)は作業ディレクトリを指定しており、-v(--volume)オプションはホストのHerokuSampleAppディレクトリをマウントしています。workdirを指定して、ホストのディレクトリをworkdir上にマウントすることで、rails newでホストのディレクトリにプロジェクトを生成することができます。

Rails用のDocker imageを作成

Dockerfileを下記のようにしました。

FROM rails:latest
MAINTAINER ishikawa_pro

ENV RAILS_ENV=production RACK_ENV=production
WORKDIR /usr/src/HerokuSampleApp
COPY ./HerokuSampleApp/Gemfile* ./
RUN bundle install
COPY ./HerokuSampleApp ./

CMD ["rails","s", "-b", "0.0.0.0"]

プロジェクトのGemfileとGemfile.lockだけを先にマウントしてbundle install しています。こうすることで、再度buildする際にGemfileが変更されていなければ、bundle installのキャッシュを利用してくれるので高速で buildが完了します。また、MOUNTでファイルをマウントせずにCOPYでrailsのプロジェクトをコピーするようにしています。これは、ホストのディレクトリをマウントしてしまうと手元の開発環境では問題ないですが、デプロイ先のコンテナでプロジェクトのディレクトリがマウントできないからです。
あと重要なことは、heroku container registryはENTRYPOINT コマンドに対応していないようなので、ENTRYPOINTではなくCMDを使わないといけないようです。

.dockerignoreの作成

docker-composeでrailsを手元で動かしている状態等でdocker buildし直してデプロイしてしまうと、railsプロジェクト内のtmp/pids/server.pidが残ったままになってしまい本番環境でrailsサーバーが起動しないという自体がよく起こるので、.dockerignoreにtmp/pids/server.pidを登録してbuildする際にdocker デーモンに送らないようにします。

./HerokuSampleApp/tmp/pids/server.pid

イメージをbuildする

docker build -t heroku_sample_app .

これで、heroku_sample_appというリポジトリ名のimageができます。

scaffold

作ったimageを使ってscafoldします。

docker run -it --rm  -v  $(pwd)/HerokuSampleApp/:/usr/src/HerokuSampleApp/ heroku_sample_app rails generate scaffold user name:string age:integer

テーブルスキーマは、

user
name:string
age:integer

です。

docker-composeによる管理

Railsコンテナの設定はここで一旦休憩です。ここからは、docker-composeでRailsコンテナとPostgreSQLコンテナを管理できるようにします。今回PostgreSQLコンテナに関しては、公式リポジトリを使うため特にDockerfileを書いたりはしません。 (PostgreSQLイメージの使い方はDocker HubのPostgreSQLのページを参照してください)
docker-compose.ymlは以下のようにしました。

version: '2'

volumes:
  pgdb:
      driver: 'local'
services:
  db:
    image: library/postgres
    ports:
      - "5432:5432"
    env_file: .env
    volumes:
      - pgdb:/var/lib/postgresql/data
    container_name: HerokuSamplePostgres
  web:
    build: .
    image: rails/heroku_sample_app:latest
    ports:
      - "3000:3000"
    env_file: .env
    environment:
      POSTGRES_HOST: db
      RAILS_ENV: development
      RACK_ENV: develobpment
    links:
      - db:postgres
    volumes:
      - ./HerokuSampleApp/:/usr/src/HerokuSampleApp/
    container_name: HerokuSampleApp
    restart: on-failure
    stdin_open: true
    tty: true

pgdbという名前のvolumeを作成して、/var/lib/postgresql/data にあるdatabase fileをvolumeにマウントさせることでデータベースのデータを永続させています。
詳しく過去の記事でまとめているので読んでみてください。
ishikawa-pro.hatenablog.com
railsコンテナの環境変数でRACK_ENVとRAILS_ENVの設定を、Dockerfile側ではproductionにしておき、docker-compose側ではdevelopmentにすることで、手元の開発環境はdevelopmentで動き、本番環境ではproductionで動くようにしています。
あと、.envファイルを読み込んでコンテナ間で共通の環境変数を設定しています。
とりえあず、postgreSQLのユーザー名とパスワードだけです。

POSTGRES_USER=ユーザー名
POSTGRES_PASSWORD=パスワード

データベース周りの設定

railsのdatabse.ymlを編集してとりあえず、postgreSQLコンテナに繋がるようにだけします。

default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV['POSTGRES_USER'] %>
  password: <%= ENV['POSTGRES_PASSWORD'] %>
  host: <%= ENV['POSTGRES_HOST'] %>
development:
  <<: *default
  database: HerokuSampleApp_development

.envにユーザー名とパスワードを書いているので、環境変数を使ってdatabase.ymlの中身を書きます。
docker-composeでコンテナ2つを立てて、rake db:createとmigrateして、データベースを作成します。

docker-compose up -d  
docker-compose exec web rails db:create
docker-compose exec web rails db:migrate

一旦動作確認

ここまでで、超雑なユーザー管理アプリ(?)みたいなものができてるはずなのでザックリ動作確認しましょう。あと、データが永続化できているか確認するためにコンテナを落として、立ち上げ直します。

docker-compose restart

データが消えてなければローカルの環境設定は完了です。

f:id:ishikawa_pro:20171214202651p:plainf:id:ishikawa_pro:20171214202655p:plain
f:id:ishikawa_pro:20171214202700p:plainf:id:ishikawa_pro:20171214202703p:plain
動作確認

Herokuへデプロイする

CLIインストール

mac 限定でいきます。Heroku CLIとContainer Registry用のプラグインをインストールします。
(mac 以外の人は、👉を参照。 https://devcenter.heroku.com/articles/heroku-cli )

brew install heroku/brew/heroku
heroku plugins:install heroku-container-registry

インストールが終われば、HerokuへのログインとContainer Registoryへログインします。

heroku login
heroku container:login

Herokuアプリの作成

プロジェクトのディレクトリで、Herokuアプリの作成をします。

heroku create

database.ymlのproduction部分を修正

Heroku postgresを使うので、database.ymlのproduction部分だけHeroku postgres用の設定に直します。

production:
  url: <%= ENV['DATABASE_URL'] %>

rails newするときに --database=postgresqlをつけておけば、databse.ymlが最初からpostgreSQL用の記述になっており、Herokuの場合の設定の仕方もコメントで書いてあるので細かい説明は省略します。

SECRET_KEY_BASE生成

SECRETE_KEY_BASEを生成して、herokuの環境変数に追加します。

SECRET_BASE_KEY=$(docker run -it --rm -v $(pwd)/HerokuSampleApp/:/usr/src/HerokuSampleApp/ rails/heroku_sample_app bundle exec rake secret)
heroku config:add SECRET_KEY_BASE=$SECRET_BASE_KEY

No app specifiedが出る場合は、--app オプションでアプリ名を入れる。アプリ名は、

heroku apps

で取得する。

リポジトリをpushする

下記のコマンドでbuildとpushをしてくれます。

heroku container:push web

Heroku Postgresの設定

Heroku Postgresを有効にするのはコマンド一発で終わりです笑

heroku addons:create heroku-postgresql:hobby-dev

Heroku Postgresの設定が終わると自動でDATABASE_URLの環境変数を追加してくれているので確認してます。ちゃんと環境変数が設定されていれば、テーブル作成します。

heroku config
heroku run rake db:migrate

動作確認

以上の作業でデプロイ完了です。ページを開いてみてうまく動いていれば完璧です。

heroku open

まとめ

記事が長くなりましたが、これで開発環境構築からデプロイまで完了です。
Heroku Container Registoryを使ってみて、自分の作ったdocker imageを使ってめちゃくちゃ簡単にデプロイできるので最高じゃん!という感じです。特にハマるところもなくすんなりデプロイできましたし、無料枠内でアプリ1つは動かせるので僕が今作ってるアプリもHerokuへデプロイしようと思います!
長くなりましたが、今日はこれで失礼します。