Open11
AWSとTerraformを勉強したくなったので触る
ピン留めされたアイテム
![横浜ベイスターズ](https://res.cloudinary.com/zenn/image/fetch/s--xei1peBE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/50284744be.jpeg)
軽い個人アプリを本番環境で運用したい話
- 以下のアプリをいい感じにデプロイしたい話を進めたい。
- Rails on Docker
- MySQL
- Redis
- AWS Cognito(Terraform済)
どういうアプリか?
- AWS Cognitoでログイン/新規登録するだけのゴミアプリ。
- RailsアプリでCognitoとの連携コードを書く必要ある?みたいな話は一旦置いておいてください。
- AWS CognitoとALBで二要素認証連携ができるため、わざわざRails上で認証コードを書く必要がないのです。
①メイン画面
- ログインと新規登録を選べます。
- ユーザー名とパスワードを入力します。
- 二要素認証を求めてくるので、Authenticator系のアプリを用いて認証コードを入力
- ログイン完了です。
![横浜ベイスターズ](https://res.cloudinary.com/zenn/image/fetch/s--xei1peBE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/50284744be.jpeg)
本番環境での運用はどうする?
- AWSサービスを利用する。
- AWS Cognitoを使用しているので、DBやRedisもAWSのサービスで統一してしまった方がまとまりがあって良い。
- Terraformで管理を可能にする。(すでにCognitoはTerraform管理している)
設計してみた
- AWS初心者すぎるので、以下のように設計してみた。
- 私が開発したアプリの場合、以下の設計になる。
- ただしこれは本来は良くない設計であることは承知している。
- 本当は以下のような設計であるべき。
- ユーザー認証周りはALBとCognitoで連携できるため。
- ただし当初の設計ミスが響き、
- Cognitoとの連携をRailsのコードで実装したためECSとCognito間を接続してユーザー認証を実現してやる必要がある。(正直ここは反省点である。)
コンテナ
- NginxコンテナとAppコンテナは分割させる。
- 開発したアプリではNginx用とApp用でコンテナを分けている。
- 1コンテナ=1インスタンスとした方がコンテナ間の関係が疎となり、問題が生じた時の切り分けや影響範囲が小さくなる。
![横浜ベイスターズ](https://res.cloudinary.com/zenn/image/fetch/s--xei1peBE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/50284744be.jpeg)
VPC
- リソースの配置、接続性、セキュリティなどを制御可能な仮想ネットワーク
- 仮想ネットワーク内のインスタンスのアクセス制限
- 独自の IP アドレス範囲の選択
- サブネットの作成
- ルートテーブルの設定
VPCネットワーク
- 上記で設計した仮想ネットワークの基盤を作成した。
- IPアドレスの範囲は/16 から /28 までの最大 5 個の CIDRで選択可能。
- サービスの範囲を考慮すると、/28 で問題ないが、サブネットの選択が/16 から /28であることから、サブネットのIP範囲より大きい範囲を指定する必要がある。
- 将来的なサービスの拡張性よりIPを枯渇させない、サブネットとIPの計算が楽になるという理由で /16 を選択した。
VPCサブネット
- 以下の三種類のサブネットを作成した。
- パブリックサブネット
- プライベートサブネット内に配置することで、インターネットからの直接アクセスを制限し、セキュリティを向上させる。
- パブリックサブネット(ダミー)
- ALBは複数のサブネットを指定することが求められる。
- プライベートサブネット
- 基本的にアプリケーションサーバーは外部からのネットワークからは届かない空間に存在することが望ましい。
- パブリックサブネット
ゲートウェイ
- 以下の二種類のゲートウェイを作成した。
- インターネットゲートウェイ
- 外部インターネットと接続する必要があるため作成。
- NATゲートウェイ
- 正直VPCエンドポイントを作成しているため、必要性は懐疑的。EC2から外部ネットワークへアウトバウンドのネットワークが必要となった場合にNATゲートウェイがあると後々便利な気がしたため作成。
- インターネットゲートウェイ
VPCエンドポイント
- ECSから外部へコンテナイメージを取得する際にECRへアクセスが必要である。
- プライベートサブネットから外部インターネットへ接続するときに、以下二点の選択肢がある。
- NATゲートウェイを利用する
- 接続が発生するたびにコストがかかる。不要なデータ処理料金は削減するなら好ましい選択ではない。
- VPCエンドポイントを作成する。
- NATゲートウェイを利用する
- 参考
![横浜ベイスターズ](https://res.cloudinary.com/zenn/image/fetch/s--xei1peBE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/50284744be.jpeg)
Error
- NATゲートウェイの一部であるインターフェイスはIPv6プレフィックス外に設定できない的なことを言っている。
- 単純に設定していたdestinationがIPv6型のパラメータだった。
- ipv4のdestination設定と正しいipv4でのデフォルトルート型にしてやれば良い。
$ terraform apply
│ Error: error creating Route in Route Table (rtb-04e5881ba4b3bc13a) with destination (::/0): InvalidParameterValue: An interface that is part of a NAT gateway cannot be the next hop for an IPv6 destination CIDR block outside the CIDR range 64:ff9b::/96 or IPv6 prefix list.
│ status code: 400, request id: 2482ace8-a2b5-4719-96af-4ef95038b6ac
│
│ with module.network.aws_route.private_nat_route,
│ on network/main.tf line 85, in resource "aws_route" "private_nat_route":
│ 85: resource "aws_route" "private_nat_route" {
![横浜ベイスターズ](https://res.cloudinary.com/zenn/image/fetch/s--xei1peBE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/50284744be.jpeg)
セキュリティグループとは
- セキュリティグループはインスタンス単位で設定する。
- AWS 上のリソースに関連づけることが可能であり、リソースレベルでの通信を制御する。
- 通信状態を記録して、ステートフルな動作(発生した通信に対する応答は自動で許可)する。
ネットワークACLとは
- ネットワークACLは サブネット単位で全インスタンスに適用するファイアウォール機能
- システムやファイル、ネットワーク上のリソースなどへのアクセス可否の設定をリストにして制御する。
- 通信状態を記録しないため、ステートレス(発生した通信に対する応答は明示的に許可しないといけない)となる。
- インバウンドとアウトバウンドの通信制御設定を明示的に設定する必要性がある。
採用
- 結論「セキュリティグループを採用」
- セキュリティグループはデフォルト通信拒否ルールである。
- 今回のインフラ構築ではリソースに対しての通信制御で十分に対応可能である、と判断した。
- ネットワークACLはネットワークACLでは同一サブネット内の通信制御設定ができない。
- セキュリティグループは送信元/先ルールをセキュリティグループIDで設定できる。
参考
![横浜ベイスターズ](https://res.cloudinary.com/zenn/image/fetch/s--xei1peBE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/50284744be.jpeg)
TerraformでApply時にエラーが発生
-
malformed = 奇形
という意味であるので、IPの設定が間違っている
Error: creating Security Group (nat): InvalidVpcId.Malformed: The vpc ID '192.168.1.0/24' is malformed
│ status code: 400, request id: cbb561c3-a3b8-4cc3-b202-5f2cb744a03d
│
│ with module.firewall.aws_security_group.nat,
│ on firewall/main.tf line 86, in resource "aws_security_group" "nat":
│ 86: resource "aws_security_group" "nat" {
- そもそもIPではなく、対象VPCを定義したIDを引き渡すべきだった。
- よくエラーを見ていると、IDが奇形であると記載している。
![横浜ベイスターズ](https://res.cloudinary.com/zenn/image/fetch/s--xei1peBE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/50284744be.jpeg)
ALB(Application Load Balancer) とは
- AWS が提供するロードバランサー
- アプリケーションレイヤーでの振り分けが可能
- SSL/TLS の暗号化/複号化
- AWS Cognito との連携(今回 Rails 側で連携スクリプトを実装したためお役御免にはなるが、将来的には...)
採用可否
- NginxコンテナがWebサーバーの役割を果たしてくれるが、果たしてELBは必要なのか
- ALBを利用することで、複数のNginxコンテナにトラフィックを均等に分散することができる。
- 正直Nginxコンテナを複数構築することはない。
- ALBは複数のアベイラビリティーゾーンにわたって可用性を提供する。
- 現状のシステムは高度な可用性は求められない。
- ALBを使用することで、SSL/TLSの終端を行うことができる。
- Nginxコンテナ上でSSL/TLSの導入は可能であるが、ALBでSSL/TLS機能を担ってくれた方が管理が簡素になる。
- セキュリティの向上
- Nginxコンテナをプライベートサブネット下に配置できる。
ALB採用の理由
ALB
- リクエストヘッダーやパスなどの情報に基づいてトラフィックをルーティングおよびバランシングできる。
- nginxコンテナに静的コンテンツを配置して、HTTPリクエストのパス次第でアプリサーバーを介さずにNginxサーバーからリクエストを返す、といった仕様が組める?
NLB
- 高速かつ効率的なロードバランシングが可能
- 高速性を求めていない。
- NLBは静的IPアドレスをサポート
- 外部クライアントやDNSレコードなどが常に同じIPアドレスに接続できる。
- 外部クライアントの連携はしない、DNSにRoute53を導入すればなんとかなる
![横浜ベイスターズ](https://res.cloudinary.com/zenn/image/fetch/s--xei1peBE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/50284744be.jpeg)
ALB作成時に発生したエラー
- どうやらALBリソースを作成するには、二つ以上のサブネットが必要とのことです。
│ Error: creating application Load Balancer: ValidationError: At least two subnets in two different Availability Zones must be specified
│ status code: 400, request id: ec68e889-09fb-49a2-afc8-ade089ca01d7
│
│ with module.lb.aws_lb.alb,
│ on lb/main.tf line 1, in resource "aws_lb" "alb":
│ 1: resource "aws_lb" "alb" {
解決
- 正直ふたつもパブリックサブネットは不要と思っている。
- しかし一旦ダミー用のパブリックサブネットを作成することで解決。
https://qiita.com/Kobayashi2019/items/0da45f21d0c27ec84559
![横浜ベイスターズ](https://res.cloudinary.com/zenn/image/fetch/s--xei1peBE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/50284744be.jpeg)
ECR(Elastic Container Registry)とは
- フルマネージドのコンテナイメージを管理するレジストリ
- ECRに登録したイメージをECSのタスク定義上で取得する
- つまりECRにはDockerイメージをアップロードする必要がある
運用
-
リポジトリに既に存在しているタグ付きイメージを不可にする運用とする。
-
image_tag_mutability
:IMMUTABLE
- タグバージョン
latest
で更新せずに、バージョン名を指定すること!- どれがlatestがわからなくなりそう
-
-
以下のコマンドを実行し、Docker イメージを ECR にコミットする。
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin [awsID].dkr.ecr.ap-northeast-1.amazonaws.com
$ docker build -t nginx-operation-app web/
$ docker tag nginx-operation-app:latest [awsID].dkr.ecr.ap-northeast-1.amazonaws.com/nginx-operation-app:0.1.0
$ docker push [awsID].dkr.ecr.ap-northeast-1.amazonaws.com/nginx-operation-app:0.1.0
![横浜ベイスターズ](https://res.cloudinary.com/zenn/image/fetch/s--xei1peBE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/50284744be.jpeg)
ECS(Elastic Container Service)とは
- Docker コンテナのデプロイや運用管理を行うための、フルマネージドなコンテナオーケストレーションサービス
タスク
- コンテナの実行単位をタスクと呼ぶ。
- どういったコンテナをどんなインスタンスで実行するかをタスク定義にて記す。
サービス
- コンテナが実行し処理が完了すると、すぐに終了する。
- ECSサービスは起動するタスクの数を定義して、指定した数のタスクを維持する。
- ALBで受け取ったリクエストとコンテナに引き渡す。
運用上の注意
- ECS を活用して ECR に登録済みのコンテナイメージをもとに Fargate 上にコンテナを稼働させる。
- ユーザーはサーバーのプロビジョニングや管理を行う必要がない。
- コンテナの実行に必要なリソース(CPU、メモリ)のみを指定する。
- そのため運用コストがEC2よりも低い。
- ユーザーはサーバーのプロビジョニングや管理を行う必要がない。
- ECSはプライベートサブネットに存在しているため、ECR用のVPCエンドポイントを作成する必要がある。 #63
- ECSにECRへのアクセス権限を付与するには、IAMロールを付与する必要がある。
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogStream",
"logs:PutLogEvents"
![横浜ベイスターズ](https://res.cloudinary.com/zenn/image/fetch/s--xei1peBE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/50284744be.jpeg)
ECSでタスクが実行できない
概要
- デプロイ前、ECRへのコンテナイメージ取得がうまくいっていない。
タスクの停止時刻: 2024-02-21T08:17:35.493Z
ResourceInitializationError: unable to pull secrets or registry auth: execution resource retrieval failed: unable to retrieve ecr registry auth: service call has been retried 3 time(s): RequestError: send request failed caused by: Post "https://api.ecr.ap-northeast-1.amazonaws.com/": dial tcp 3.112.64.17:443: i/o timeout. Please check your task network configuration.
service my-service is unable to consistently start tasks successfully. For more information, see the Troubleshooting section of the Amazon ECS Developer Guide.
考えられる原因(仮説)
- ECRからイメージをプルするためのIAMロールがECSヘ割り当てられていない。
- ECS→VPCエンドポイント→ECRへの疎通ができていない。
│ Error: failed creating ECS Task Definition (task-operation-app): ClientException: Fargate requires task definition to have execution role ARN to support ECR images.
│
│ with module.ecs.aws_ecs_task_definition.ecs_task,
│ on ecs/main.tf line 9, in resource "aws_ecs_task_definition" "ecs_task":
│ 9: resource "aws_ecs_task_definition" "ecs_task" {
│
╵