【Rails】form_withで非同期通信して表示を動的に変更(data-remote="true")
概要
リストを表示するときに追加読み込み、並び替え、絞り込みなどで表示を動的に変更したいことがしばしばある。
そんな時に、:remote => true、.js.erbなどを利用して対応する。
また、そのままだと:remote=>trueをしてもJSリクエストとして処理されず、.js.erbではなく.html.erbが描画されてしまったのでrails-ujsを使うようにする。
環境
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a)
Rails 7.0.3.1
Bundler version 2.3.7
完成物
https://github.com/di-kotobuki/hatena/tree/ajax
・動作の様子
data-remote="true"
フォームやリンクのhtml要素にdata-remote="true"の属性が付くと通信がJSリクエストとなって非同期通信になる。
これの何がいいかと言うと、画面がリロードされずに画面の一部の要素を再描画できたりすることだと思う。
VueやReactを使うなら不要になる。
やり方
link_toであればremote: trueを、form_withであればlocal: falseを設定すればいい。
Rails で JavaScript を利用する - Railsガイド
route
適当なgetとpostのルートを作成。
Rails.application.routes.draw do get "/articles", to: "articles#index" post "/articles", to: "articles#create" end
controller
上記で参照しているarticlesコントローラーとそのメソッドを作成。
コマンド一つでcontrollerやviewを作ってくれるのでそれを編集していく。
$ rails generate controller Articles index --skip-routes
class ArticlesController < ApplicationController def index @articles = ["aaa", "bbb", "ccc"] end def create @article = params[:title] respond_to do |format| format.html { redirect_to articles_path } format.js end end end
view
indexでレンダリングされるindex.html.erbは先のコマンド実行で作成されているので適当に加筆。
<Articles#index> <Find me in app/views/articles/index.html.erb> <div> <%= form_with url: articles_path, local: false do |f| %> <%= f.text_field :title %> <%= f.submit %> <% end %> <%= render "list", articles: @articles %> </div>
リスト部分をテンプレートに分割する(_list.html.erb)。
<ul id="articles"> <% articles.each do |article| %> <li><%= article %></li> <% end %> </ul>
createでレンダリングされるのはcontroller側で特に指定していなければcreate.js.erbがレンダリングされる。これは自分でファイルを作成する。
内容は、formで入力された値でhtml要素を追加するというもの。
(() => { document.getElementById("title").value = ""; const target = document.getElementById("articles"); const el = document.createElement("li"); el.textContent = "<%= @article %>"; target.appendChild(el); })();
rails-ujs
ここまでの状態でrails -sで起動して確認してみると、submitしても何もページが再読み込みされるだけで何も起こらない。
本来formt.jsでcreate.js.erbが読み込まれるはずが、format.htmlでindexへのリダイレクトになってしまっている。
この理由は、submit時のリクエストのContent-TypeでMIMEタイプがtext/htmlになっているからで、これはrails-ujsを適用すれば解決するらしい。
この辺りの通信に関する部分はRailsのバージョンで異なっていたりするらしいので、調べるときはRailsのバージョンをちゃんと見た方がいい。
下記を参考にする。
javascript - Rails UJS not firing with Rails 7 - Stack Overflow
プロジェクトルートで実行。
$ ./bin/importmap pin @rails/ujs
app/javascript/application.jsに追記。
import Rails from '@rails/ujs'; Rails.start();
おしまい
これで完成物の出来上がり。
【Rails】Macでの環境構築(エラー対応も)
概要
macOSでRailsの開発作業ができるようにするまでの手順。
macには元からrubyとbundlerが入っているが、バージョン管理をしたいのでrbenvでrubyをインストールしてそちらを適用させる。
あと、途中のrbenv installの時にエラーが出たのでその対応。
環境
macOS Big Sur 11.4
MacBook Air(Retina, 13-inch, 2019)
プロセッサ Intel Core i5
メモリ 8GB
手順
バージョン管理用のrbenvをインストール
rbenvで好きなバージョンのrubyをインストール
gemで好きなバージョンのrailsをインストール
rbenvをインストール
brew install rbenv ruby-build
brew installできたら.bashrcや.zshrcに下記を追記
PATH="$HOME/.rbenv/bin:$PATH" eval "$(rbenv init -)"
.bashrcや.zshrcを再読み込みして(source ~/.bashrc)、rbenv -vでrbenvのバージョンを確認できたらOK
rubyをインストール
rbenvでインストールできるrubyのバージョンを確認する
rbenv install --list 2.6.10 2.7.6 3.0.4 3.1.2 jruby-9.3.6.0 mruby-3.1.0 picoruby-3.0.0 rbx-5.0 truffleruby-22.1.0 truffleruby+graalvm-22.1.0 Only latest stable releases for each Ruby implementation are shown. Use 'rbenv install --list-all / -L' to show all local versions.
今回は一番新そうな3.1.2をインストールする(そこそこ時間かかります)
rbenv install 3.1.2
私はここでエラーが出ました。
これだけ見てもわからないのでログ全文を確認する(/var/folders/....../.log)。
下のスクショはログの前半部分、残りはエラー解決に関係なさそう。
デカデカと表示されているOpenSSL関連に問題がありそう。
インストールされているOpenSSLを確認していく。
$ which openssl /usr/bin/openssl $ openssl version -a LibreSSL 2.8.3 built on: date not available platform: information not available options: bn(64,64) rc4(16x,int) des(idx,cisc,16,int) blowfish(idx) compiler: information not available OPENSSLDIR: "/private/etc/ssl"
LibreSSLとでているが、これはmacOSに標準搭載されているものらしい。
以前、homebrewでインストールした気がするのでそちらの状態も確認する。
$ brew list ==> Formulae autoconf ca-certificates gettext git m4 openssl@1.1 pcre2 pkg-config pyenv rbenv readline ruby-build xz $ brew info openssl@1.1 openssl@1.1: stable 1.1.1q (bottled) [keg-only] Cryptography and SSL/TLS Toolkit https://openssl.org/ /usr/local/Cellar/openssl@1.1/1.1.1m (8,081 files, 18.5MB) Poured from bottle on 2021-12-15 at 23:34:25 /usr/local/Cellar/openssl@1.1/1.1.1q (8,097 files, 18.5MB) Poured from bottle on 2022-07-13 at 12:40:34 From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/openssl@1.1.rb License: OpenSSL ==> Dependencies Required: ca-certificates ✔ ==> Caveats A CA file has been bootstrapped using certificates from the system keychain. To add additional certificates, place .pem files in /usr/local/etc/openssl@1.1/certs and run /usr/local/opt/openssl@1.1/bin/c_rehash openssl@1.1 is keg-only, which means it was not symlinked into /usr/local, because macOS provides LibreSSL. If you need to have openssl@1.1 first in your PATH, run: echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrc For compilers to find openssl@1.1 you may need to set: export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib" export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include" For pkg-config to find openssl@1.1 you may need to set: export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig" ==> Analytics install: 1,225,966 (30 days), 3,060,362 (90 days), 11,394,164 (365 days) install-on-request: 51,798 (30 days), 122,474 (90 days), 473,105 (365 days) build-error: 1,766 (30 days)
openssl@1.1を使うには色々と環境変数を設定しなければいけないようだ。
まとめて.zshrcに追記してしまう。
export PATH="/usr/local/opt/openssl@1.1/bin:$PATH" export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib" export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include" export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"
.zshrcを読み込んでOpenSSLがどうなったか確認する。
$ which openssl /usr/local/opt/openssl@1.1/bin/openssl $ openssl version -a OpenSSL 1.1.1q 5 Jul 2022 built on: Tue Jul 5 09:08:33 2022 UTC platform: darwin64-x86_64-cc options: bn(64,64) rc4(16x,int) des(int) idea(int) blowfish(ptr) compiler: clang -fPIC -arch x86_64 -O3 -Wall -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAESNI_ASM -DVPAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPOLY1305_ASM -D_REENTRANT -DNDEBUG OPENSSLDIR: "/usr/local/etc/openssl@1.1" ENGINESDIR: "/usr/local/Cellar/openssl@1.1/1.1.1q/lib/engines-1.1" Seeding source: os-specific
これでrbenv install 3.1.2をしてもエラーが変わりませんでした。
調べてみると--with-openssl-dirというオプションでrubyをインストールするときのopensslを指定できるらしい。
# ディレクトリは$ which opensslで確認したopenssl@1.1までのパス export RUBY_CONFIGURE_OPTS="--with-openssl-dir=/usr/local/opt/openssl@1.1"
やっとインストールできました。
$ rbenv install 3.1.2 Downloading ruby-3.1.2.tar.gz... -> https://cache.ruby-lang.org/pub/ruby/3.1/ruby-3.1.2.tar.gz Installing ruby-3.1.2... ruby-build: using readline from homebrew Installed ruby-3.1.2 to /Users/otam/.rbenv/versions/3.1.2 $ rbenv versions * system 3.1.2
作業ディレクトリ内で3.1.2を適用する。
$ rbenv local 3.1.2 $ rbenv rehash $ rbenv versions system * 3.1.2 $ ruby -v ruby 3.1.2
railsをインストール
$ gem install rails $ rails -v Rails 7.0.3.1
アプリケーションを作成する。
$ rails new hatena
アプリケーション起動
$ cd hatena $ rails s => Booting Puma => Rails 7.0.3.1 application starting in development => Run `bin/rails server --help` for more startup options Puma starting in single mode... * Puma version: 5.6.4 (ruby 3.1.2-p20) ("Birdie's Version") * Min threads: 5 * Max threads: 5 * Environment: development * PID: 15596 * Listening on http://127.0.0.1:3000 * Listening on http://[::1]:3000 Use Ctrl-C to stop
エラーの対応で結構時間がかかった。
もう深夜3時だ、明日は平日だから問題あるね。
でもエラー解決できたのは嬉しかったからよし。
【AWS】 Amazon MemoryDB for Redis vs ElasticCache
概要
チャット機能を実装する際によく利用されているRedisだが、AWSを利用する場合はどのサービスを選択すべきかがよくわかっていなかったので調べた。
選択肢
redisで検索すると以下の2つがヒットする。
- Amazon MemoryDB for Redis
- ElasticCache
これらのどちらを利用すべきか。
例えばチャット機能を実装したい場合、「AWS チャット」みたいに検索するとElasticCacheを利用するモデルが検索結果には多い。
ただ、MemoryDBの方が後発サービスらしく、こちらを使った方が良さそうにも思える。
比較検討
しばらく調べてみて一番参考になりそうだったのが↓
https://cloudwellserved.com/amazon-elasticache-for-redis-vs-amazon-memorydb-for-redis/
ElasticCache
AWSのサービス概要にも「In-Memory Cache」とあるように、データをメモリにキャッシュとして保存して取り扱うサービス。
本来データベースにデータを取りに行くところを、キャッシュがあればそちらを取りに行かせることができて、データベースへのアクセスを減らせて処理も軽くなるから速度改善できるよ、というサービスのようだ。
決してデータベースではなく、あくまでデータベースのキャッシュとしての立ち位置であり、AWS上ではRDSと共に運用されるサービスである。
また、読み込みはキャッシュからできるが、書き込みはデータベースに対して行わなければならず、その後にキャッシュの更新も必要。
単体で利用するならばセッションの保持とかできそう。
Amazon MemoryDB for Redis
AWSのサービス概要にも「Fully managed, Redis-compatible, in-memory database service」とあるように、データベースサービスである。
RDS + ElasticCacheで運用していたものをまとめて一つのサービスとしたようなもの。
データの読み書き共にこのサービスに対して実行すればよく、こちらの方がデータベースとしてRedisを使いたいという利用目的に適っている。
料金はどこを見てもElasticCacheの約1.5倍と見積もっているところが多い気がする。
結論
セッションの保持などのブラウザ上でいうCookieのような利用がしたいのであれば、ElasticCache単体で良さそう。
チャットなどのデータベースに対する読み書きのような利用がしたいのであれば、ElasticCache + RDSよりも運用しやすそうなAmazon MemoryDB for Redisを使いたい。
備忘録(go-swaggerでAPIサーバを作る、gorm, air, dockerと共に)
GoでREST APIを作ります。
go-swaggerはyamlでWebAPIの定義をすることで、サーバーサイド・クライアントサイドのコードをコマンドの実行で生成してくれる。
今回はサーバーサイドだけ欲しいのでクライアントサイドは生成しない。
ただし、リクエストを受けた時にパラメータに従ってデータベースとやりとりする部分の処理(handler)は自身で実装する必要がある。
Railsとかでいうcontrollerのメソッドが中身空っぽな状態で必要なだけ自動生成され、その中身を自分で実装するみたいな感じ?
データベースはmysql。
DockerでAPI用のコンテナとデータベース用のコンテナを起動させる。
また、コード編集でホットリロードさせるためにAPIの起動にはairを使う。
- ディレクトリ構成
- Docker
- go-swagger導入
- swagger.yamlを作成
- swagger.ymlからコードを生成
- .air.tomlの編集
- ハンドラを作成
- 環境変数設定
- データベース接続
ディレクトリ構成
最終的にこうなる(ディレクトリと使うファイルのみ表示)
gen以下が自動生成されたコードで、ルートディレクトリからだとgo run api/server/gen/cmd/xxx/main.go
で起動できる。
gen以下の生成されたコードは編集不可だが、restapi内のconfigure_xxx.goだけは編集可能。起動時処理やミドルウェアを記述したりするもので、このファイル存在時にgo-swaggerによるコード生成を行っても上書きされないから編集することができる。
root ├─ api │ ├─ server │ │ ├─ gen │ │ │ ├─ cmd │ │ │ │ └─ xxx │ │ │ │ └─ main.go │ │ │ ├─ models │ │ │ └─ restapi │ │ │ ├─ operations │ │ │ └─ configure_xxx.go │ │ └─ handlers │ │ └─ 自分で作成したhandler │ ├─ swagger │ │ └─ swagger.yaml │ ├─ .air.toml │ ├─ go.mod │ ├─ go.sum │ └─ Dockerfile ├─ config │ └─ config.go ├─ db │ └─ Dockerfile └─ docker-compose.yml
Docker
Dockerfileとdocker-compose.ymlを作成
# api/Dockerfile FROM golang:1.16-alpine RUN apk update && apk add git # ホットリロードのためにairを導入 RUN go get github.com/cosmtrek/air WORKDIR /usr/src/app COPY api . RUN go mod download && go mod verify EXPOSE 8000
# db/Dockerfile FROM mysql:5.7 COPY db/my.conf /etc/mysql/conf.d EXPOSE 3306
# db/my.conf [mysqld] character-set-server=utf8mb4 collation-server = utf8mb4_bin general_log=1 general_log_file=/var/log/mysql/mysql.log log-error=/var/log/mysql/mysql-error.log [mysqld] character-set-server=utf8mb4 [client] character-set-server=utf8mb4
# docker-compose.yml version: "3" services: api: build: context: . dockerfile: api/Dockerfile container_name: api environment: - DB_DRIVER=mysql - DB_DATABASE=dev - DB_USER=user - DB_PASSWORD=password - DB_HOST=db - DB_PORT=3306 volumes: - api:/usr/src/app ports: - 8000:8000 tty: true command: /bin/sh -c "air -c .air.toml" depends_on: - db db: build: context: . dockerfile: db/Dockerfile container_name: db environment: - MYSQL_DATABASE=dev - MYSQL_USER=user - MYSQL_PASSWORD=password - MYSQL_ROOT_PASSWORD=password - TZ=Asia/Tokyo ports: - 3306:3306
go-swagger導入
cd api go mod init github.com/{githubのユーザー名}/{githubのリポジトリ名} go get -u github.com/go-swagger/go-swagger/cmd/swagger
swagger.yamlを作成
go-swaggerはOpenAPI2.0対応で、3.0は対応していないので注意
VScodeを使っているならば、Swagger Viewerというプラグインが便利
# api/swagger/swagger.yaml --- swagger: "2.0" info: version: 1.0.0 title: Test basePath: /api/v1 shemes: - http paths: /user: get: tags: - user produces: - application/json responses: 200: desciption: List of users schema: type: array items: $ref: "#/definitions/User" default: description: Unexpected error schema: $ref: "#/definitions/Error" definitions: User: type: object properties: id: type: integer example: 1 name: type: string example: テスト名前 email: type: string example: test@gmail.com created_at: type: string updated_at: type: string deleted_at: type: string Error: type: object properties: code: type: integer example: 400 message: type: string example: bad request
swagger.ymlからコードを生成
これによってgen以下にコードが生成される
cd api mkdir -p server/gen swagger generate server --strict-additional-properties -t ./server/gen -f ./swagger/swagger.yaml
.air.tomlの編集
起動するのに生成されたmain.goを参照するように変更し、hostとportを指定する
hostはデフォルトが127.0.0.0でDockerコンテナ外からアクセスするのに不便なので0.0.0.0を指定
portは起動するたびにランダムでホットリロードされるたびにポートが変化して面倒なので8000を指定
# api/.air.toml cmd = "go build -o ./tmp/main ./server/gen/cmd/xxx" full_bin = "APP_ENV=dev APP_USER=air ./tmp/main --host 0.0.0.0 --port 8000"
ハンドラを作成
この時点でdocker-compose up -d
で起動できるが、localhost:8000/api/v1/user
にアクセスすると「operation user.GetUser has not yet been implemented」と返ってくる
自動生成されたコードを見るとapi/server/gen/restapi/configure_xxx.goでapi.UserGetUserHandlerが定義されていないことがわかる。
なのでハンドラを作成して設定する。
# api/server/handers/user.go type UserHandler struct {} func NewUserHandler() UserHandler { return UserHandler{} } func (uh *UserHandler) GetUser(params user.GetUserParams) middleware.Responder { ver us []*models.User return user.NewGetUserOK().WithPayload(us) }
# api/server/gen/restapi/configure_xxx.go # configureAPI内に下記を追加 uh := handlers.NewUserHandler() api.UserGetUserHandler = user.GetUserHandlerFunc(uh.GetUser)
これでlocalhost:8000/api/v1/user
にアクセスするとhanderのWithPayloadで設定した空の配列が返ってくる
環境変数設定
docker-compose.ymlのenvironmentに記述した環境変数を読み込んでおく
# api/config/config.go package config type Config struct { DbDriver string DbDatabase string DbUser string DbPassword string DbHost string DbPort string } func NewConfig() Config { return Config{ DbDriver: os.Getenv("DB_DRIVER"), DbDatabase: os.Getenv("DB_DATABASE"), DbUser: os.Getenv("DB_USER"), DbPassword: os.Getenv("DB_PASSWORD"), DbHost: os.Getenv("DB_HOST"), DbPort: os.Getenv("DB_PORT"), } }
データベース接続
dockerで起動しているdbへ接続する。
接続情報は先の環境変数。
今回はgormを使用する。
go get -u gorm.io/gorm go get -u gorm.io/driver/mysql
# app/database/database.go package database type DatabaseClient struct { c *config.Config } func NewDatabaseClient(config *config.Config) DatabaseClient { return DatabaseClient{ c: config, } } func (dc *DatabaseClient) ConnectDatabaseClient() *gorm.DB { driver := dc.c.DbDriver dsn := fmt.Sprintf( "%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", dc.c.DbUser, dc.c.DbPassword, dc.c.DbHost, dc.c.DbPort, dc.c.DbDatabase, ) db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { log.Fatal("Failed to connect database: ", err.Error()) } return db } func (dc *DatabaseClient) CloseDatabaseClient(db *gorm.DB) { sqlDb, _ := db.DB() if err := sqlDb.Close(); err != nil { log.Fatal("Failed to close connection database: ", err.Error()) } }
# api/server/gen/restapi/configure_xxx.go # configureAPIを編集(下記は編集後の最終形態) func configureAPI(api *operations.xxxAPI) http.Handler { // 下記を追記 c := config.NewConfig() dc := database.NewDatabaseClient(&c) db := dc.ConnectDatabaseClient() // さっきhandler作成の時に追加した記述(引数にdbを持たせる) uh := handlers.NewUserHandler(db) api.UserGetUserHandler = user.GetUserHandlerFunc(uh.GetUser) // 以下は初期のまま api.ServerError = errors.ServerError api.UseSwaggerUI() api.JSONConsumer = runtime.JSONConsumer() api.JSONProducer = runtime.JSONProducer() api.PreServerShutdown = func() {} // CloseDatabaseClientを追記 api.ServerShutdown = func() { dc.CloseDatabaseClient(db) } return setupGlobalMiddleware(api.Serve(setupMiddleware)) }
# api/server/handlers/user.go # データベースから情報を取得するように編集(下記は編集後の最終形態) # DBを操作できるように変更 type UserHandler struct { db *gorm.DB } # 引数にdbを受け取るように変更 func NewUserHandler(db *gorm.DB) UserHandler { return UserHandler{ db: db, } } # DBからデータを取得するように変更 func (uh *UserHandler) GetUser(params user.GetUserParams) middleware.Responder { ver us []*models.User res := uh.db.Find(&us) if err := res.Error; err != nil { return user.NewGetUserDefault(500) } return user.NewGetUserOK().WithPayload(us) }
これでlocalhost:8000/api/v1/user
にアクセスすれば、データベースに登録した情報を取得できる。
備忘録(ECR + ECS で複数コンテナ)
各自のPCではdocker-composeを使ってAPI用のコンテナとフロント用のコンテナを作成して開発しているものを、AWS ECSとECRでデプロイする。
AWS上ではdocker-composeが使われるわけではなく、それぞれのコンテナを別々にdockerコマンドで起動しているような感じになる。
なので、コンテナの数だけECRでリポジトリ・ECSでタスクとサービスを設定する必要がある。
docker-compose.ymlで記述しているような環境変数やポートの設定はECR・ECSの設定としてコンソール上で入力していく。
参考
これらを見ながら作業をしました。
Dockerに慣れてきた人がECSで複数コンテナのデプロイをしてみる - Qiita
Dockerコンテナで作ったアプリをECS+RDSでデプロイする - Qiita
手順
Dockerはイメージがあって、それを元にコンテナを作成して、そのコンテナを起動する。
そして、イメージはECR・コンテナの作成と起動はECSで設定するので、ECRでリポジトリを作成してからECSでタスク定義とサービスの設定をする。
ECR
Create repository
ECRでDockerイメージを管理するリポジトリを作成する。
API用とフロント用で二つのDockerfileを元としたDockerイメージのために二つのリポジトリを作成する。
- Visibility setttings:Private
- Repository name:任意(プロジェクト名-コンテナ名とかが無難)
- Tag immutability:Disabled
- KMS encryption: Disabled
Createで作成完了。
Push
作成したリポジトリを見るとView push commandsとあるので、それに倣えばいい。
AWS CLIをインストールしておく必要がある。
aws ecr get-login-password --region {リージョン} | docker login --username AWS --password-stdin {AWSアカウントID}.dkr.ecr.{リージョン}/amazonaws.com
# AWS CLIのデフォルトプロファイル以外を使用する場合はプロファイルを指定する
# aws ecr get-login-password --region {リージョン} --pofile {プロファイル名} | docker login --username AWS --password-stdin {AWSアカウントID}.dkr.ecr.{リージョン}/amazonaws.com docker build -t {リポジトリ名} . # Dockerfileがルートディレクトリにない場合はパスを指定する
# dokcer build -t {リポジトリ名} -f {Dockerfileのパス} . dokcer tag {リポジトリ名}:latest {AWSアカウントID}.dkr.ecr.{リージョン}.amazonaws.com/{リポジトリ名}:latest docker push {AWSアカウントID}.dkr.ecr.{リージョン}.amazonaws.com/{リポジトリ名}:latest
ECS
クラスター作成→タスク定義→サービス設定
クラスターはdockerインストール済のEC2インスタンスだと思うと楽。
今回は、一つのクラスター内にAPI用のサービスとフロント用のサービスを作成する。
タスクはどのECRリポジトリのイメージを使用してコンテナを作成するかとか環境変数を設定したりする。
サービスはクラスター内でどのタスクを動かすかを決めるようなもの。docker-compose.ymlのservicesだと思うと楽かもしれない。(厳密には全然違うかもしれないけど)
frontendとbackendの二つのサービスになることが多いと思う。
Create Cluster
Step 1: Select cluster template
AWS Fargateでいい場合はNetworking onlyを選択。
Fargateは軽いEC2とでも思えばいい。
EC2みたいにssh接続はできないので異常時に中で確認したりはできない。あと、dockerコンテナのポートをホストで別のポートに結びつけることもできない。(8080:3000みたいな)
開発環境であればFargate、本番環境であればEC2でいいと思う。
Step 2: Configure cluster
- Cluster name:任意
- Create VPC:チェック
- Tags:無記入
- CloudWatch Container Insights:チェック
Createで作成完了。
Create new Task Definition
API用とフロント用で二つ作成する。
Step2のTask definition nameとTask execution role、Add containerの入力内容以外は同じ。
Step 1: Select launch type compatibility
たぶんクラスターと同じやつを選べばいい。
FARGATEを選択。
Step 2: Configure task and container definitions
- Task definition name:任意
- Requires compatibilities:FARGATE(Step 1で選んだやつ)
- Task role:未選択(IAMロールが必要なら設定)
- Network mode:awsvpc
- Operating system family:Linux
- Task execution role:なければ作成(2回目以降ならば初回で作ったものを選択)
- Task memory (GB):0.5GB
- Task CPU (vCPU):0.25 vCPU
- Add Container:ボタンを押すとコンテナに関する入力欄が表示される
Container name:任意
Image:ECRのURIをコピー
Port mappings:コンテナがListenするポート
Environment variables:環境変数を設定(docker-compose.ymlのenvironmentやenv_fileの内容)
その他の項目はそのままにした。 - Service integration:未チェック
- Proxy configuration:未チェック
- Log router integration:未チェック
- Volumes:未設定
- Tags:無記入
Createで作成完了。
Create Service
API用とフロント用で二つ作成する。
Step1のTask DefinitionでAPI用・フロント用それぞれ対応するタスクを設定。
Step2のSecurity groupはAPI用とフロント用で分けるため別々の設定。
Step 1: Configure service
- Launch type:FARGATE(クラスターと同じ)
- Operating system family:Linux
- Task Definistion:作ったタスクを選択
- Platform version:LATEST
- Cluster:作ったクラスター
- Service name:任意
- Service type:REPLICA
- Number of tasks:1
- Minimum healty percent:100
- Maximum percent:200
- Deployment circuit breaker:Disabled
- Deployment type:Rolling update
- Enable ECS managed tags:チェック
- Propagate tags from:Do not propagate
Step 2: Configure network
- Cluster VPC:クラスター作成時に自動作成されたVPCを選択
- Subnets:クラスター作成時に自動生成されたサブネットを全て選択
- Security groups:そのまま(新規作成)
- Auto-assign public IP:ENABLED(あとで変更できないので注意)
- Load balancer type:None(本番環境であればApplication Load Balancerを選択した方がいい)
- Service discovery (optional):API用のみチェック
Service discovery (API用のみ)
各自のPCでdocker-composeを使用するときは、コンテナ内から別コンテナへアクセスのにコンテナ名を使用(http://api:8080みたいに)すればできる。
サービスディスカバリーはそのような設定をすることができ、Namespace nameにlocal・Service discovery nameにapiと設定すれば、local.api:portでフロント用コンテナ内からAPI用コンテナにアクセスできる。
- Enable service discovery integration:チェック
- Namespace:create new private namespace
- Namespace name:任意
- Configure service discovery service:Create new service discovery service
- Service discovery name:任意
- Enable ECS task health propagation:チェック
- DNS record type:A
- TTL:10
Step 3: Set Auto Scaling (optional)
本番であればちゃんと設定したほうがいいと思う。
- Service Auto Scaling:Do not adjust th service's desired count
Step 4: Review
Step 1~3でしてきた設定を確認。
Create Serviceで作成完了。
クラスターでServicesがACITIVE・TasksがRUNNINGになっていることを確認できたらデプロイ完了。
Security Groups
これまでの過程でセキュリティグループが自動作成されているが、デフォルトではポートが80番しか空いていないので、コンテナがListenしているポートを開放する。
また、データーベースでRDSを使用する場合はAPI用のコンテナからのアクセスを許可する必要がある。
備考
今回作成したECS・ECRをgithubのプッシュを検知して自動デプロイさせる場合は、こちらを参照。(CodeBuild→CodePipeline)
備忘録(Go + Air in Docker)
dockerコンテナで go run main.go をした後に、ホスト側のローカルで修正しても go run し直すかdockerコンテナを再起動するしないと反映されない。
Airを使うと変更を随時反映させることができる。(ホットリロード・live reload)
Realizeというのもあるが使ったことはない。
Dockerfile
GitHub - cosmtrek/air: ☁️ Live reload for Go apps
FROM golang:1.16-alpine RUN apk update && apk add git RUN go get github.com/cosmtrek/air@latest WORKDIR /usr/src/app COPY go.mod go.sum . RUN go mod download && go mod verify COPY . . EXPOSE 8080
docker-compose.yml
コマンドを go run main.go ではなく air -c .air.toml としてairで起動させる。
.air.tomlは設定ファイルで下記を編集。
air/air_example.toml at master · cosmtrek/air · GitHub
version: "3" services: go: build: context: . dockerfile: Dockerfile container_name: go volumes: - ./:./ ports: - 8080:8080 tty: true command: /bin/sh -c "air -c .air.toml"
あとは、dockerコンテナを起動すれば編集を随時反映してくれる。