こんにちは、エンジニアの田代です。
前回記事(https://blog.tech-monex.com/entry/2020/01/31/163001)時点では証券企画室所属でしたが、今年度からマネックス・ラボ所属となりました。
やる事は変わらず、ferciの開発に携わっています。
はじめに
個人的な感覚ですが、数年前までは「本番用ではなく、あくまで開発環境のための物だ」とする意見が大多数であったように思われるDockerコンテナも、徐々に本番導入のハードルが下がってきているのではないでしょうか。
AWSのコンテナオーケストレーションサービスであるECSにおいても、サービスディスカバリがサポートされたり、Fargateのエフェメラルストレージ暗号化が標準となったりするなど、使用性が向上してきています。
そこで今回は、nginxのWebフロントとTomcatのバックエンドによって構成されるシステムを、ECS+Fargateを使用して構築するためのCloudFormationテンプレートを作成してみたいと思います。
やりたいこと
上図の通りのnginx+Tomcatの環境をFargate上に構築します。
今回はどちらも1サービスにつき1コンテナのシンプルな構成ですが、バックエンドのTomcatの前段にELBを置かず、サービスディスカバリを使用してサービス名+ドメイン名でnginxからアクセスしている部分がポイントとなります。
前提事項
当記事で扱う全ての技術的要素について解説すると膨大な文章量になってしまうので、下記を前提事項とします。
- VPC、フロントELB用のログ転送先のS3バケット、各サブネット及びセキュリティグループが用意されていること
- Docker及びCloudFormation(以下CFn)の操作方法に関する知識
- nginxのconfファイルの記述内容に関する知識
- ECS、ECR、Fargateに関する基本的な理解
ECRリポジトリとDockerコンテナの作成
まずはecr.yaml
をCFnで実行して、Dockerコンテナを登録するためのECRリポジトリを作成します。
ACLの設定は省略していますが、他AWSアカウントからアクセスさせたい等の場合は追記してください。
AWSTemplateFormatVersion: "2010-09-09" Resources: SampleNginxRepository: Type: AWS::ECR::Repository Properties: RepositoryName: sample-nginx Tags: - Key: Name Value: SampleNginxRepository SampleTomcatRepository: Type: AWS::ECR::Repository Properties: RepositoryName: sample-tomcat Tags: - Key: Name Value: SampleTomcatRepository
次に、nginxとTomcatの各コンテナをビルドして、ECRにプッシュします。
殆ど設定らしい設定は行なっていませんが、nginx/default.conf
の6行目、リバースプロキシの設定で向き先をhttp://サービス名.ドメイン名:8080/
としている部分がポイントです。
FROM nginx:latest ADD "default.conf" "/etc/nginx/conf.d/"
server { listen 80; server_name localhost; location /app/ { proxy_pass http://backend.sample.local:8080/; } location / { root /usr/share/nginx/html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
FROM tomcat:latest
各ディレクトリとファイルが用意出来たら、CLIから下記のコマンドを実行してください。
$ aws ecr get-login --no-include-email --region {YOUR_REGION}
$ docker login -u AWS -p {YOUR_TOKEN} https://{YOUR_ECR_URL}.amazonaws.com
$ # -> Login Succeeded
$ docker build -t {sample-nginx|sample-tomcat} .
$ docker tag {sample-nginx|sample-tomcat}:latest {YOUR_ECR_URL}.amazonaws.com/{sample-nginx|sample-tomcat}:latest
$ docker push {YOUR_ECR_URL}.amazonaws.com/{sample-nginx|sample-tomcat}:latest
下図のように、nginxとTomcatの各リポジトリにコンテナが登録されたことがECRの画面から確認できれば成功です。
ECSリソースの作成
続いて、ECS関連のリソースを構築して行きます。
まずfront_elb.yaml
をCFnで実行して、Webフロント用のinternet-facingなELBを作成します。前提事項にある通り、サブネットやセキュリティグループ等は事前に適切な設定が用意されているものとします。
AWSTemplateFormatVersion: "2010-09-09" Parameters: VpcId: Type: String Subnet1: Type: String Subnet2: Type: String S3Bucket: Type: String S3Prefix: Type: String ElbSecurityGroup: Type: String Resources: FrontNginxElb: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: FrontNginxElb Scheme: internet-facing Subnets: - !Ref Subnet1 - !Ref Subnet2 SecurityGroups: - !Ref ElbSecurityGroup LoadBalancerAttributes: - Key: access_logs.s3.enabled Value: true - Key: access_logs.s3.bucket Value: !Ref S3Bucket - Key: access_logs.s3.prefix Value: !Ref S3Prefix - Key: "idle_timeout.timeout_seconds" Value: 30 Tags: - Key: Name Value: FrontNginxElb FrontNginxTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: HealthCheckIntervalSeconds: 30 HealthCheckPath: "/" HealthCheckPort: 80 HealthCheckProtocol: HTTP HealthCheckTimeoutSeconds: 5 HealthyThresholdCount: 5 Matcher: HttpCode: "200" Name: FrontNginxTargetGroup Port: 80 Protocol: HTTP Tags: - Key: Name Value: FrontNginxTargetGroup TargetGroupAttributes: - Key: deregistration_delay.timeout_seconds Value: 300 TargetType: ip UnhealthyThresholdCount: 2 VpcId: !Ref VpcId FrontNginxListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - TargetGroupArn: !Ref FrontNginxTargetGroup Type: forward LoadBalancerArn: !Ref FrontNginxElb Port: 80 Protocol: HTTP
最後にecs.yaml
をCFnで実行して、各ECSリソースと、バックエンド用の名前空間及びサービスディスカバリを作成します。
ポイントは105行目と115行目で、これらの値をnginx/default.conf
内proxy_passに設定したhttp://サービス名.ドメイン名:8080/
と一致させることで、nginxからFQDNでTomcatへアクセスすることが可能となります。
AWSTemplateFormatVersion: "2010-09-09" Parameters: VpcId: Type: String TaskExecutionRoleArn: Type: String NginxImageId: Type: String TomcatImageId: Type: String NginxTargetGroupArn: Type: String FrontSecurityGroup: Type: String BackendSecurityGroup: Type: String FrontSubnet: Type: String BackendSubnet: Type: String Resources: SampleEcsCluster: Type: "AWS::ECS::Cluster" Properties: ClusterName: SampleEcsCluster SampleEcsLogGroup: Type: "AWS::Logs::LogGroup" Properties: LogGroupName: "/ecs/logs/sample" SampleFrontTask: Type: "AWS::ECS::TaskDefinition" Properties: Cpu: 256 ExecutionRoleArn: !Ref TaskExecutionRoleArn Family: SampleFrontTask Memory: 512 NetworkMode: awsvpc RequiresCompatibilities: - FARGATE ContainerDefinitions: - Name: FrontNginxContainer Image: !Ref NginxImageId LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref SampleEcsLogGroup awslogs-region: !Ref "AWS::Region" awslogs-stream-prefix: "front" PortMappings: - HostPort: 80 Protocol: tcp ContainerPort: 80 SampleBackendTask: Type: "AWS::ECS::TaskDefinition" Properties: Cpu: 256 ExecutionRoleArn: !Ref TaskExecutionRoleArn Family: SampleBackendTask Memory: 512 NetworkMode: awsvpc RequiresCompatibilities: - FARGATE ContainerDefinitions: - Name: BackendTomcatContainer Image: !Ref TomcatImageId LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref SampleEcsLogGroup awslogs-region: !Ref "AWS::Region" awslogs-stream-prefix: "backend" PortMappings: - HostPort: 8080 Protocol: tcp ContainerPort: 8080 SampleFrontService: Type: AWS::ECS::Service Properties: Cluster: !Ref SampleEcsCluster DesiredCount: 1 LaunchType: FARGATE LoadBalancers: - TargetGroupArn: !Ref NginxTargetGroupArn ContainerPort: 80 ContainerName: FrontNginxContainer NetworkConfiguration: AwsvpcConfiguration: AssignPublicIp: ENABLED SecurityGroups: - !Ref FrontSecurityGroup Subnets: - !Ref FrontSubnet ServiceName: front TaskDefinition: !Ref SampleFrontTask SampleDnsNamespace: Type: AWS::ServiceDiscovery::PrivateDnsNamespace Properties: Name: sample.local Vpc: !Ref VpcId BackendServiceDiscovery: Type: AWS::ServiceDiscovery::Service Properties: DnsConfig: DnsRecords: - Type: A TTL: 60 Name: backend NamespaceId: !Ref SampleDnsNamespace SampleBackendService: Type: AWS::ECS::Service Properties: Cluster: !Ref SampleEcsCluster DesiredCount: 1 LaunchType: FARGATE NetworkConfiguration: AwsvpcConfiguration: AssignPublicIp: ENABLED SecurityGroups: - !Ref BackendSecurityGroup Subnets: - !Ref BackendSubnet ServiceName: backend TaskDefinition: !Ref SampleBackendTask ServiceRegistries: - ContainerName: BackendTomcatContainer RegistryArn: !GetAtt BackendServiceDiscovery.Arn
実行が完了したら、Webフロント用のELBにブラウザからアクセスしてみましょう。
/
でnginxのデフォルト画面が、/app
でTomcatのデフォルト画面が表示されれば成功です。
また、念のためRoute 53でsample.localのレコードセットを確認してみると、バックエンドのコンテナのAレコードが自動で登録されていることが分かります。
まとめ
以上で、 サービスディスカバリを使用することで、バックエンド用のELBを作成することなく、タイトルの通りの環境を構築することができました。
実際にはこれらに加えてAutoScalingの設定等も必要になると思いますが、同様の構成のCFnテンプレートを作成する際の参考になれば幸いです。
過去2回インフラ寄りの内容になったので、次回は開発寄りの内容の記事が書ければと思っています。