CloudFormationテンプレート(JSON) – CloudTrail 設定

1. 概要

2. 各リソースメモ

3. JSONテンプレート

CloudTrail.json
{
    "AWSTemplateFormatVersion" : "2010-09-09",
    "Description" : "CloudTrail",
     "Resources" : {
        "S3Bucket" : {
            "Type" : "AWS::S3::Bucket",
            "Properties" : {
                "BucketName" : { "Fn::Join" : [ "", [ "cloudtrail-", { "Ref" : "AWS::AccountId" } ] ] }
            }
        },
        "S3BucketPolicy" : {
            "Type" : "AWS::S3::BucketPolicy",
            "Properties" : {
                "Bucket" : { "Ref" : "S3Bucket"},
                "PolicyDocument" : {
                    "Version" : "2012-10-17",
                    "Statement" : [
                        {
                            "Sid" : "AWSCloudTrailAclCheck20150319",
                            "Effect" : "Allow",
                            "Principal" : { "Service" : "cloudtrail.amazonaws.com"},
                            "Action" : "s3:GetBucketAcl",
                            "Resource" : { "Fn::Join" : [ "", [ "arn:aws:s3:::cloudtrail-", { "Ref" : "AWS::AccountId" } ] ] }
                        },
                        {
                            "Sid" : "AWSCloudTrailWrite20150319",
                            "Effect" : "Allow",
                            "Principal" : { "Service" : "cloudtrail.amazonaws.com" },
                            "Action" : "s3:PutObject",
                            "Resource" : { "Fn::Join" : [ "", [ "arn:aws:s3:::cloudtrail-", { "Ref" : "AWS::AccountId" }, "/AWSLogs/", { "Ref" : "AWS::AccountId" }, "/*" ] ] },
                            "Condition" : { "StringEquals" : { "s3:x-amz-acl" : "bucket-owner-full-control" } }
                        }
                    ]
                }
            }
        },
        "IAMRole" : {
            "Type" : "AWS::IAM::Role",
            "Properties" : {
                "AssumeRolePolicyDocument" : {
                    "Version" : "2012-10-17",
                    "Statement" : [
                        {
                            "Sid" : "",
                            "Effect" : "Allow",
                            "Principal" : {
                                "Service" : "cloudtrail.amazonaws.com"
                            },
                            "Action" : "sts:AssumeRole"
                        }
                      ]
                },
                "Path" : "/",
                "Policies" : [
                    {
                        "PolicyName" : "cloudtrail",
                        "PolicyDocument" : {
                            "Version" : "2012-10-17",
                            "Statement" : [
                                {
                                    "Sid" : "AWSCloudTrailCreateLogStream20141101",
                                    "Effect" : "Allow",
                                    "Action" : [ "logs:CreateLogStream" ],
                                    "Resource" : { "Fn::Join" : [ "", [ "arn:aws:logs:", { "Ref" : "AWS::Region" }, ":", { "Ref" : "AWS::AccountId" }, ":log-group:CloudTrail:log-stream:", { "Ref" : "AWS::AccountId" }, "_CloudTrail_", { "Ref" : "AWS::Region" }, "*" ] ] }
                                },
                                {
                                    "Sid" : "AWSCloudTrailPutLogEvents20141101",
                                    "Effect" : "Allow",
                                    "Action" : [ "logs:PutLogEvents" ],
                                    "Resource" : { "Fn::Join" : [ "", [ "arn:aws:logs:", { "Ref" : "AWS::Region" }, ":", { "Ref" : "AWS::AccountId" }, ":log-group:CloudTrail:log-stream:", { "Ref" : "AWS::AccountId" }, "_CloudTrail_", { "Ref" : "AWS::Region" }, "*" ] ] }
                                }
                            ]
                        }
                    }
                ],
                "RoleName": "cloudtrail"
            }
        },
        "LogsLogGroup": {
            "Type" : "AWS::Logs::LogGroup",
            "Properties" : {
                "LogGroupName" : "CloudTrail",
                "RetentionInDays" : 7
            }
        },
        "CloudTrailTrail" : {
            "DependsOn" : "S3BucketPolicy",
            "Type" : "AWS::CloudTrail::Trail",
            "Properties" : {
                "CloudWatchLogsLogGroupArn" : { "Fn::GetAtt" : [ "LogsLogGroup", "Arn" ] },
                "CloudWatchLogsRoleArn" : { "Fn::GetAtt": [ "IAMRole", "Arn" ] },
                "IncludeGlobalServiceEvents" : true,
                "IsLogging" : true,
                "IsMultiRegionTrail" : true,
                "S3BucketName" : { "Fn::Join": [ "", [ "cloudtrail-", { "Ref" : "AWS::AccountId" } ] ] },
                "TrailName" : { "Ref": "AWS::AccountId" }
            }
        }
    },
    "Outputs" : {
        "S3Bucket" : {
            "Description" : "S3 Bucket Name",
            "Value" : { "Ref" : S3Bucket }
        },
        "IAMRole" : {
            "Description" : "IAM Role Name",
            "Value" : { "Ref" : IAMRole }
        },
        "LogsLogGroup" : {
            "Description" : "Log Group Name",
            "Value" : { "Ref" : LogsLogGroup }
        },
        "CloudTrailTrail" : {
            "Description" : "Trail Name",
            "Value" : { "Ref" : CloudTrailTrail }
        }
    }
}

続きを読む

CloudFormationテンプレート(JSON) – AWS Config 設定

1. 概要

2. 各リソースメモ

3. JSONテンプレート

AWSConfig.json

{
    "AWSTemplateFormatVersion" : "2010-09-09",
    "Description" : "AWS Config",
    "Resources" : {
        "S3Bucket" : {
            "Type" : "AWS::S3::Bucket",
            "Properties" : {
                "BucketName" : { "Fn::Join" : [ "", [ "awsconfig-", { "Ref" : "AWS::AccountId" } ] ] }
            }
        },
        "S3BucketPolicy" : {
            "Type" : "AWS::S3::BucketPolicy",
            "Properties" : {
                "Bucket" : { "Ref": "S3Bucket"},
                "PolicyDocument" : {
                    "Version" : "2012-10-17",
                    "Statement" : [
                        {
                            "Sid" : "AWSConfigBucketPermissionsCheck",
                            "Effect" : "Allow",
                            "Principal" : {
                                "Service" : [
                                    "config.amazonaws.com"
                                ]
                            },
                            "Action" : "s3:GetBucketAcl",
                            "Resource" : { "Fn::Join" : [ "", [ "arn:aws:s3:::awsconfig-", { "Ref" : "AWS::AccountId" } ] ] }
                        },
                        {
                            "Sid" : " AWSConfigBucketDelivery",
                            "Effect" : "Allow",
                            "Principal" : {
                                "Service" : [
                                    "config.amazonaws.com"
                                ]
                            },
                            "Action" : "s3:PutObject",
                            "Resource" : { "Fn::Join" : [ "", [ "arn:aws:s3:::awsconfig-", { "Ref" : "AWS::AccountId" }, "/AWSLogs/", { "Ref" : "AWS::AccountId" }, "/Config/*" ] ] },
                            "Condition" : {
                                "StringEquals" : {
                                    "s3:x-amz-acl" : "bucket-owner-full-control"
                                }
                            }
                        }
                    ]
                }
            }
        },
        "SNSTopic" : {
            "Type" : "AWS::SNS::Topic",
            "Properties" : {
                "DisplayName" : "AWS Config Notification Topic",
                "TopicName" : "awsconfig"
            }
        },
        "SNSSubscription" : {
            "Type" : "AWS::SNS::Subscription",
            "Properties" : {
                "Endpoint" : "test@example.com",
                "Protocol" : "email",
                "TopicArn" : { "Ref" : "SNSTopic"}
            }
        },
        "IAMRole" : {
            "Type" : "AWS::IAM::Role",
            "Properties" : {
                "AssumeRolePolicyDocument" : {
                    "Version" : "2012-10-17",
                    "Statement" : [
                        {
                            "Sid" : "",
                            "Effect" : "Allow",
                            "Principal" : {
                                "Service" : "config.amazonaws.com"
                            },
                            "Action" : "sts:AssumeRole"
                        }
                    ]
                },
                "ManagedPolicyArns" : [ "arn:aws:iam::aws:policy/service-role/AWSConfigRole" ],
                "Path" : "/",
                "Policies" : [
                    {
                        "PolicyName" : "awsconfig",
                        "PolicyDocument" : {
                            "Version" : "2012-10-17",
                            "Statement" : [
                                {
                                    "Effect" : "Allow",
                                    "Action" : [
                                        "s3:PutObject*"
                                    ],
                                    "Resource" : { "Fn::Join" : [ "", [ "arn:aws:s3:::awsconfig-", { "Ref" : "AWS::AccountId" }, "/AWSLogs/", { "Ref" : "AWS::AccountId" }, "/*" ] ] },
                                    "Condition" : {
                                        "StringLike" : {
                                            "s3:x-amz-acl" : "bucket-owner-full-control"
                                        }
                                    }
                                },
                                {
                                    "Effect" : "Allow",
                                    "Action" : [ "s3:GetBucketAcl" ],
                                    "Resource" : { "Fn::Join" : [ "", [ "arn:aws:s3:::awsconfig-", { "Ref" : "AWS::AccountId" } ] ] }
                                },
                                {
                                    "Effect" : "Allow",
                                    "Action" : "sns:Publish",
                                    "Resource" : "arn:aws:sns:ap-northeast-1:777813037810:awsconfig",
                                    "Resource" : { "Fn::Join" : [ "", [ "arn:aws:sns:ap-northeast-1:", { "Ref" : "AWS::AccountId" }, ":", { "Fn::GetAtt" : [ "SNSTopic", "TopicName" ] } ] ] }
                                }
                            ]
                        }
                    }
                ],
                "RoleName" : "awsconfig"
            }
        },
        "ConfigConfigurationRecorder" : {
            "Type" : "AWS::Config::ConfigurationRecorder",
            "DependsOn" : [ "S3Bucket", "SNSTopic" ],
            "Properties" : {
                "Name" : { "Fn::Join" : [ "", [ "awsconfig-", { "Ref" : "AWS::AccountId" } ] ] },
                "RecordingGroup" : {
                    "AllSupported" : true,
                    "ResourceTypes" : [],
                    "IncludeGlobalResourceTypes" : true
                },
                "RoleARN" : { "Fn::GetAtt" : [ "IAMRole", "Arn" ] }
            }
        },
        "ConfigDeliveryChannel" : {
            "Type" : "AWS::Config::DeliveryChannel",
            "DependsOn" : [ "S3Bucket", "SNSTopic" ],
            "Properties" : {
                "Name" : { "Fn::Join" : [ "", [ "awsconfig-", { "Ref" : "AWS::AccountId" } ] ] },
                "S3BucketName" : { "Ref" : "S3Bucket" },
                "SnsTopicARN" : { "Ref" : "SNSTopic" }
          }
        }
    },
    "Outputs" : {
        "S3Bucket" : {
            "Description" : "S3 Bucket Name",
            "Value" : { "Ref" : S3Bucket }
        },
        "SNSTopic" : {
            "Description" : "SNS Topic Name",
            "Value" : { "Fn::GetAtt" : [ "SNSTopic", "TopicName" ] }
        },
        "IAMRole" : {
            "Description" : "IAM Role Name",
            "Value" : { "Ref" : IAMRole }
        },
        "ConfigConfigurationRecorder" : {
            "Description" : "Config ConfigurationRecorder Name",
            "Value" : { "Ref" : ConfigConfigurationRecorder }
        },
        "ConfigDeliveryChannel" : {
            "Description" : "Config DeliveryChannel Name",
            "Value" : { "Ref" : ConfigDeliveryChannel }
        }
    }
}

続きを読む

Athenaで入れ子のjsonにクエリを投げる方法が分かりづらかったので整理する

Kinesis FirehoseでS3に置かれた圧縮したjsonファイルを、それに対してクエリを投げる、というのを検証してたのですが、Hive素人なのでスキーマの作り方もクエリの投げ方も正直あんまり良くわかってませんでした。

そこで下記を参照しながらスキーマの作成とクエリ投入をやってみて、最終的にうまくいきました。

日本語記事
https://aws.amazon.com/jp/blogs/news/create-tables-in-amazon-athena-from-nested-json-and-mappings-using-jsonserde/

元記事
https://aws.amazon.com/jp/blogs/big-data/create-tables-in-amazon-athena-from-nested-json-and-mappings-using-jsonserde/

ずーっと日本語記事を読みながらやっていたのですが、これがめちゃくちゃわかりづらい!!!
※理解度には個人差があります

多分知っている人が見たら何となくわかるんでしょうが、恐らくこれを見るのは自分みたいにあまり良く知らないので参考にしながら実際にやってみている、という層だと思います。
最終的に上手く行ってから思ったのは、前提知識がないと読むのがしんどい、ということですね…。
ただもう少し書いといてくれるだけで十分なのに…。
原文も軽く見ましたが、そっちにも書いてないのでそもそも記述されてません。

調べてもまだ中々情報が出てこない上に、クエリ投入時にエラーが出た場合もエラーメッセージが淡白すぎてどこが問題でエラーになってるのかさっぱりわからなくて悪戦苦闘してました。

そんなわけで、今後同じところで困る人が一人でも減るように、自分用メモも兼ねてハマったところについて補足をしておきたいと思います。

概要

リンク先で書いてあることの流れは大まかに下記のとおりです。

  1. FirehoseでSESの送信イベントログをS3に保存する
    送信イベントログはjson形式で、それをFirehoseでS3に保存しています。
  2. Athenaのテーブルを作成して、クエリを投げる
    • ただテーブル作成して投げる場合
    • 入れ子になっているjsonに対してテーブル作成してクエリを投げる場合
    • 禁止文字を含んでいるものに対してテーブルを作成してクエリを投げる場合
      わかりづらいですが、禁止文字を含む項目をマッピングする項目とクエリを投げる項目が分かれています。)
  3. hive-json-schemaの紹介
    jsonからテーブル作成のためのクエリを生成するツールっぽいのですが、紹介してるわりにちっとも使い方が書いてません…。
    使い方の解説をどなたか…。

ハマったところ

入れ子になったjsonに対するテーブル作成について

ハマったところといいつつ、自分はこの辺は割とスムーズに行ったのですが、ちょっとわかりづらいかもしれないので念のため。
サンプルにもありますが、jsonの中にまたjsonとか配列とかが入っている、みたいなケースは多くあります。
そういった場合、内部にあるjsonに対してstruct型を使って、その下の項目について型を定義してやればOKです。
その中にさらにjsonがある場合はさらにその中にstruct型で定義をすればOK。
例にあるものだと、内部にmail{~}とjsonがあり、その中にさらにいくつかのjsonがあるので、それぞれに対してstruct型で定義をしています。
以下引用(全文は貼っていないので、元はリンク先を見てください。)
※一部バッククオート(`)で囲われている項目がありますが、予約語として使われている言葉をそのまま使用するとエラーになるそうです。
そのため、バッククオートで囲うことによってエスケープしてるようです。

抜粋した入れ子の部分
 mail struct<`timestamp`:string,
              source:string,
              sourceArn:string,
              sendingAccountId:string,
              messageId:string,
              destination:string,
              headersTruncated:boolean,
              headers:array<struct<name:string,value:string>>,
              commonHeaders:struct<`from`:array<string>,to:array<string>,messageId:string,subject:string>
              > 

禁止文字そのものについて

まず、禁止文字が色々あることを最初大して理解してませんでした。
項目名(↑の例だと、timestampとかsourceとかのところ)の定義に使用できない文字があります。
記事中だと「:」(コロン)が禁止文字列なので、それがクエリ中の該当箇所に入っているとエラーになります。
あとは「-」(ハイフン)なんかも禁止文字のようです。
例えばHTTPリクエストのログを見たとき、ヘッダとかはハイフンを使った項目がいくつもあったりするので困りますよね。
一応記事中の例では両方「_」(アンダースコア)に変換しています。(コロンにしか触れてませんが…。)
最初は禁止文字があると知らず、なぜエラーになっているかわからずにハマってました。
この辺どっかにまとまってるのかな…?
どうやって回避するかというと、それがWITH SERDEPROPERTIESの部分です。

禁止文字を含む場合のマッピングの仕方について

最初見た時はなんでこんなことをするのかわかりませんでしたが、上記の通り項目名を定義するときに禁止文字が入っているとエラーになります。
なので、WITH SERDEPROPERTIESの項目で、禁止文字列を含んだ項目名を、禁止文字列のない文字列にマッピングし、元のjsonのkeyでは禁止文字列を含んでいたものに対し、テーブル上ではカラム名として別の文字列をあてがうことができます。
記事中では、コロンやハイフンをアンダースコアに変換した文字列にマッピングしています。
式の左側がカラム名に使いたい文字列で、それに対して右側がデータの元の実際の名前です。
"mapping.カラム名に使いたい文字列"="実際の名前" みたいに記述してます。

マッピングの仕方

WITH SERDEPROPERTIES (
  "mapping.ses_configurationset"="ses:configuration-set",
  "mapping.ses_source_ip"="ses:source-ip", 
  "mapping.ses_from_domain"="ses:from-domain", 
  "mapping.ses_caller_identity"="ses:caller-identity"
  )

クエリの投げ方

これもまあおまけで書いておくと、ここまでしっかりと下の項目までテーブルを定義しておくと、下の項目までクエリで引っ張ることが出来ます。
記事中では下記のような例が出ています。

元記事にある例
SELECT eventtype as Event,
         mail.timestamp as Timestamp,
         mail.tags.ses_source_ip as SourceIP,
         mail.tags.ses_caller_identity as AuthenticatedBy,
         mail.commonHeaders."from" as FromAddress,
         mail.commonHeaders.to as ToAddress
FROM sesblog2
WHERE eventtype = 'Bounce'

mail{〜}の下の項目を参照する時は上記のようにドットをつけて該当項目の名前を指定しています。
さらにその下の項目を参照する時はその後ろにさらにドットをつけています。
この辺は直感的にわかりやすいかもしれません。

おまけとかtipsとか

Firehoseで配置されたフォルダ構成ではパーティションを自動で切ってもらえない

hiveではフォルダが/bucketname/path/to/log/year=YY/month=MM/day=dd/foo
みたいな構成だと自動でパーティション設定してくれるらしいのですが、FirehoseでS3にデータ配置すると/bucketname/path/to/log/YYYY/MM/DD/fooみたいになるので、自分でパーティションを作成する必要があります。
パーティションがない状態でクエリを投げても1件も引っかかりません。
これを作るには下記のようなクエリを投げる必要があります。

elbログを対象としたテーブルにパーティションを作成する場合
ALTER TABLE database_name.table_name
ADD PARTITION (year='2016',month='08',day='28')
location 's3://elb-access-log/AWSLogs/00000000000000/elasticloadbalancing/ap-northeast-1/2016/08/28/';

※参考
https://qiita.com/r4-keisuke/items/d3d339b76d4368b6b30a

上記の例だと1日ずつパーティションを設定する必要があるのですが、
パーティション数には上限があるらしい(1テーブル20000まで)ので、1日ずつとか、1時間ずつとかフォルダ分けしている場合はちょっと注意が必要かもしれないです。
※パーティションの上限については下記
https://docs.aws.amazon.com/ja_jp/general/latest/gr/aws_service_limits.html#limits_glue
さすがに対象が多すぎとなるとしんどいので、シェルスクリプトとかで回すといいと思います。
ただ、シェルスクリプト自体も1つ1つの処理実行だとそこそこ時間かかるのと、パーティションを設定するためのクエリでクエリ履歴が埋め尽くされるのが難点です。

データ元にない項目を定義しても値がnullになるだけで問題はない

jsonの出力が一定じゃなくて、いくつかの似たような型のjsonが混ざっていたり、ものによって存在しない項目があったりしても、それらのキーを全て網羅するようにまとめて定義しちゃって問題ないみたいです。
定義したけどデータ元に項目がない場合はnullが入るだけのようで。
逆に元データにある項目を全部定義する必要はないので、元データにあっても使わないような項目はテーブル作成の段階で定義しないようにしてもいいみたいですね。

ざっと書いたので、わかりづらいとか、もっとこうすればみたいな指摘があればいただけると嬉しいです。

続きを読む

Amazon ECSのログをCloudWatchで収集する

ecs01.png

全体の流れ

  1. ローカルでコンテナを動かす
  2. ECSでコンテナを動かす
  3. ECSでコンテナをクラスタリングする
  4. ECSのログをCloudWatchで収集する ←今回の投稿
  5. HTTPS化する

もくじ

今回の投稿ではコンテナのログをCloudWatchで収集します。

  1. CloudWatchでロググループを作成する
  2. タスク定義でログの設定を行う
  3. ログを閲覧する

1. CloudWatchでロググループを作成する

log01.png
   log02.png

log03.png

2. タスク定義でログの設定を行う

  • ログ設定awslogsを選択する
  • awslogs-groupにロググループ名を入力する
  • awslogs-regionにリージョンコードを入力する
  • awslogs-stream-prefixにプレフィックスを入力する(任意)

参考:awslogs-region
リージョンとアベイラビリティーゾーン – Amazon Elastic Compute Cloud

log04.png

3. ログを閲覧する

log05.png
log06.png

続きを読む

ECSで運用していた社内ツールをFargate化したときに、ハマりやすかった3つのポイント

AWS Fargate Advent Calendar 2017の25日目の記事になります。

他の方が詳細な見解や調査など行われているので、大トリの記事がこれぐらいの内容で大丈夫なのかビビりながら書いています。
ひとまず、年末年始でFargateを試してみようかなあという方の参考になれば。

1.どんな環境をFargate化したか

fargate.png

上記のような構成でECSで運用していた社内ツールのprpr(※)をFargate化しました。
現在のFargateの制限としては、

  1. 東京リージョンがない
  2. SLAがない
  3. 知見が少ない

ということで、production環境にいきなり入れるというよりは、こうしたサービスレベルの低い社内ツールから移行するのがよいかと思います。

※1 SLA設定されてました Amazon Compute サービスレベルアグリーメントを Amazon ECS および AWS Fargate に拡張

※2 prprについては、下記のブログを参照
prprでGithubのPullRequestレビュー依頼をSlack通知する

2.ハマったところ

2-1.FargateがECRのコンテナイメージをpullできない

デプロイしたECSのステータスが、延々とSTOPPEDを繰り返して、ECSのログを見ると、下記のようなエラーが出力され続けているときがありました。残念ながら、Fargate化されても、デプロイ失敗したときなど、ECSが再起動しまくるのは、自前で何とか検知する仕組みを作らないといけなさそう…
ss 2017-12-24 11.51.14.png

解決例:Fargate側にPublicIPを付与する

Fargate: CannotPullContainer located on ECS registry
にもあるように、FargateはVPC内部で起動してくるため、VPC外部への通信経路を確保しておかないと、FargateがECRからコンテナイメージをおとしてくることができません。特にセキュリティ上などで問題なければ、AssignPublicIpを有効化しておきましょう。

CFn例
Service:
    Type: AWS::ECS::Service
    Properties:
        ServiceName: !Ref RoleName
        Cluster: !Ref ECSCluster
        DesiredCount: 1
        LaunchType: FARGATE
        TaskDefinition: !Ref ECSTask
        LoadBalancers:
            -
                ContainerName: !Sub ContainerName
                ContainerPort: 3000
                TargetGroupArn: !Ref ALBTargetGroup
        NetworkConfiguration:
            AwsvpcConfiguration:
                AssignPublicIp: ENABLED
                SecurityGroups:
                    - !Ref ECSSecurityGroup
                Subnets: !Ref SubnetIds

2-2.ECSの動的ポートマッピングは使えない

Fargateのデプロイ中は、下記の画像のような感じで、同一ポートでプライベートIPが異なるという状況になる。Fargateが使用するサブネットでプライベートIPが枯渇したときにどうなるかは未検証。Fargateの起動コンテナ数の制限とかないのであれば、サブネットマスクの設計とかはちょっと注意しておいた方がよさそう。
ss 2017-12-25 7.30.10.png

解決例:ホスト側のポートを固定する

CFn例
ECSTask:
    Type: AWS::ECS::TaskDefinition
    Properties:
        Family: !Ref FamilyName
        NetworkMode: awsvpc
        RequiresCompatibilities:
            - FARGATE
        Cpu: 256
        Memory: 512
        ExecutionRoleArn: !Sub "arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole"
        ContainerDefinitions:
            -
                Name: !Ref TaskName
                Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${RegistoryName}:${ImageTag}"
                PortMappings:
                    -
                        ContainerPort: 3000
                        HostPort: 3000
                Essential: "true"
                Ulimits:
                    -
                        Name: nofile
                        SoftLimit: 65535
                        HardLimit: 65535
                Environment:
                    -
                        Name: PORT
                        Value: 3000
                    -
                        Name: RACK_ENV
                        Value: production
                LogConfiguration:
                    LogDriver: awslogs
                    Options:
                        awslogs-group: !Ref CloudWatchLogGroup
                        awslogs-region: !Sub ${AWS::Region}
                        awslogs-stream-prefix: !Ref ImageTag

2-3.CodePipelineでFargateのデプロイを行う、CFnの記述方法がわからない

AWS CodePipeline に Amazon ECS および AWS Fargate のサポートを追加
CodePipeline で ECS にデプロイできるようになり、Docker 環境の継続的デリバリも簡単になりました
にもあるのですが、12/12に、CodePipeline上でFargateのデプロイがサポートされています。
ss 2017-12-25 16.47.19.png
ただ、上記のようなイメージで、CodePipelineとFargateを連携させようとしたときに、CFnのドキュメントからだとCFnでのサンプルが見つけられませんでした。
ひとまず、下記のように書いたら、CFnでも何とか通ったけど、合ってるのかしら(どこかに公式チュートリアルとか準備されてるかな)

解決例:CodeBuildでimagedefinitions.jsonを出力して、CodePipelineのDeployフェーズと連携させる

buildspec.yml例
version: 0.2

phases:
    pre_build:
        commands:
            - $(aws ecr get-login --region $AWS_DEFAULT_REGION)
            - REPOSITORY_URI="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_REPO_NAME}"
            - IMAGE_TAG=${CODEBUILD_RESOLVED_SOURCE_VERSION}
    build:
        commands:
            - echo Build started on `date`
            - docker build -t "${IMAGE_REPO_NAME}:${IMAGE_TAG}" .
            - docker tag "${IMAGE_REPO_NAME}:${IMAGE_TAG}" "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_REPO_NAME}:${IMAGE_TAG}"
    post_build:
        commands:
            - echo Build completed on `date`
            - docker push "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_REPO_NAME}:${IMAGE_TAG}"
            - printf '[{"name":"container-name-sample","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
artifacts:
    files:
        - imagedefinitions.json
    discard-paths: yes

imagedefinitions.jsonをCodeBuildで生成したフォルダ直下においておけば、
あとは下記のようなCodePipelineの書き方で、Fargateでもデプロイが可能になる。

CFn例
CodePipeline:
    Type: AWS::CodePipeline::Pipeline
    DependsOn: CodePipelineS3
    Properties:
        Name: codepipeline-sample
        ArtifactStore:
            Type: S3
            Location: !Ref S3BucketName
        RoleArn: !Ref RoleArn 
        Stages:
            -
                Name: Source
                Actions:
                    -
                        Name: Source
                        RunOrder: 1
                        ActionTypeId:
                            Category: Source
                            Owner: ThirdParty
                            Version: 1
                            Provider: GitHub
                        Configuration:
                            Owner: hoge
                            Repo: fuga
                            Branch: master
                            OAuthToken: xxxxxxxxxxxx
                        OutputArtifacts:
                            - Name: Source
            -
                Name: Build
                Actions:
                    -
                        Name: CodeBuild
                        RunOrder: 1
                        InputArtifacts:
                            - Name: Source
                        ActionTypeId:
                            Category: Build
                            Owner: AWS
                            Version: 1
                            Provider: CodeBuild
                        Configuration:
                            ProjectName: !Ref CodeBuild
                        OutputArtifacts:
                            - Name: Build
            -
                Name: Deploy
                Actions:
                    -
                        Name: Deploy
                        ActionTypeId:
                            Category: Deploy
                            Owner: AWS
                            Version: 1
                            Provider: ECS
                        InputArtifacts:
                            - Name: Build
                        Configuration:
                            ClusterName: !Ref ClusterName
                            ServiceName: !Ref ServiceName

3.参考記事

他の方のアドベントカレンダーがすごく参考になったので、CFnまわりの実装で参考にさせていただいた記事をいくつか紹介させていただこうと思います。

Fargate を試した感想と ecs-deploy で Fargate にデプロイできるようにする話
AWS CloudFormationを使ってAWS Fargateの環境を作成してみる
ECS+EC2で動いているサービスをFargateにのせ替える

4.まとめ

社内で使っていたECSをFargate化したことで、EC2の管理(障害対応、およびセキュリティアップデート対応)をなくすことができました。VPCのサブネット設計など、Fargateにしてもインフラ面を意識しないといけないところはありそうなので、また知見がたまれば共有させていただこうと思います。

それでは、皆様メリークリスマス!

続きを読む

CloudFormationを使ってECS環境とそのデプロイシステムを作成する

こんにちは、LIFULLのchissoです。
この記事は、私が勤務するLIFULLのAdvent Calender1の24日目の記事です。

今日はクリスマス・イブですね。みなさまいかがお過ごしでしょうか。
私はQiitaに2本Advent Calenderの記事を上げています。

もう一つはこちらにAthenaの記事を書いています。

さて、早速ですが本題です。
今年の夏頃、AWSで新規サービスを作成する機会がありました。そこで、CloudFormation(以下CFn)を使って、デプロイシステムとElasticContainerService(以下ECS)のサービスを管理する仕組みを作りました。ベースはawslabsが公開しているコチラのリポジトリです。

当時、Qiitaやクラスメソッドさんのブログに大変お世話になりながらなんとかサービスインにこぎつけたのですが、自分なりに要点やハマりどころなど解説したいと思います。

私が当時調べて取り入れたこと、ハマったことなどつらつらと書くのでかなり長いです。
いざやろうと思われた際に参照してもらえれば幸いです。

はじめに

まずはじめに、なぜCFn・ECSを使ったのか簡単に述べておきます。

CFn

こちらは、AWS内のネットワーク・サービス構成もコードで管理したかった、という点につきます。もう流行りというのも憚れますが、Infrastucture as a Codeというやつですね。メリットとしては、

  1. 変更を追跡可能になる

    • 誰がいつ変更したかわからないSecurity GroupやNetwork ACLがなくなる
  2. 開発環境で作った構成がそのまま本番環境で再現できる
    • AWSコンソールはよくできていますが、ポチポチなど人の作業が発生する以上どうしても作業漏れが発生します

思いつくデメリットは、CFn templateの読み書きが辛いことでしょうか。書くときはひたすらAWSの公式ドキュメントとにらめっこです。ただし、慣れてしまえばドキュメントがよくできているので、結構スラスラ書けるようになっていきます。

ECS

こちらは、実行環境を開発環境と本番環境で揃えたい、という点です。またしても解説不要かと思いますが、dockerを使いたいだけです。
Elastic Beanstalk(EB)とどちらを採用するか結構迷ったのですが、ECRを使わないEBではbuildとdeployが一体化してしまうことが気になり、ECRを使うならECSでいいじゃん、となりました。
今年のre:InventでFargateが発表されましたし、ECSにしておいてよかったなと思っているところです。

前提となる知識

CfnとECSがどんなものか、という導入については、優れた記事がすでにたくさんありますので紹介しながら少しだけ補足します。

Cfn

CFnテンプレートは、jsonとyamlの両方で書くことが可能です。

複数のコンポーネントをCFnで管理しようとすると、1つのテンプレートが肥大化してしまいます。しかしaws cliを使うことで、複数のテンプレートに各コンポーネントを定義することが可能です(統合用のテンプレートが必要となります)。

余談ですが、このaws cloudformation packageコマンドは非常に便利で、lambdaファンクションなども参照してアップロードしてくれます。

ECS

ECSについては、はじめ用語が少しわかりにくいですが、下記の認識で良いと思います。

用語 説明
クラスター サービスが稼働するEC2インスタンス(群)
サービス タスクとクラスター、ELB(ターゲットグループ)を紐付ける。EBでいうアプリケーションのような感じ。
タスク コンテナの集まりで、docker-compose.ymlのイメージ。

CfnでECSのデプロイシステムを構築する

本題に入ります。

ベースはawslabsのリポジトリに公開されているものです。
AWSの公式ブログClassMethodさんの記事で、メリットや概略は既に紹介されているので、各yamlの役割や、私が変更を行ったところについて目的と変更方法、また細かいハマりどころを書いていきます。

全体像

ecs-refarch-continuous-deployment.yaml
 |- vpc.yaml
 |- load_balancer.yaml
 |- ecs_cluster.yaml
 |- service.yaml
 - deployment-pipeline.yml

ecs-refarch-continuous-deployment.yamlが全体像(Stack)を定義したCFn templateで、その他のyamlがネストしたStackとなっています。

ecs-refarch-continuous-deployment.yaml内に相対パスで各yamlを記載して、上述したaws cloudformation packageを実行すると、各yamlがS3へアップロードされた上でecs-refarch-continuous-deployment.yamlのパスがS3パスへ書き換えられます。
※ awslabsのecs-refarch-continuous-deployment.yamlはリポジトリ内の子templateを別でS3にあげてあり、初めからそちらを参照するように書かれています。

// before
Cluster:
  Type: AWS::CloudFormation::Stack
  Properties:
    TemplateURL: templates/ecs-cluster.yml
    Parameters:
      VpcId: !Ref VpcId

// after
Cluster:
  Properties:
    Parameters:
      VpcId:
        Ref: VpcId
    TemplateURL: https://s3.amazonaws.com/cfn-templates/{hash値}.template
  Type: AWS::CloudFormation::Stack

また、この構成の場合ecs-refarch-continuous-deployment.yamlがWeb console(またはAPI)から実行するテンプレートとなり、その際に各種パラメータ(Parametersブロックで定義したもの)を与えます。
ecs-refarch-continuous-deployment.yamlからネストしたテンプレートへのパラメータの引き渡しや、ネストしたテンプレート間での値のやりとりもecs-refarch-continuous-deployment.yaml上で行われることになります。

VPC

https://github.com/awslabs/ecs-refarch-continuous-deployment/blob/master/templates/vpc.yaml

VPC, route table, subnetsなど、ネットワーク周りの基本的な定義を行っています。

私は既存のVPC内にECSクラスタを使いたかったため、こちらは利用しませんでした。
OutputsにSubnetsとVpcIdがある通り、別templateでそれらを利用しますが、既存VPC/Subnetのidが利用可能なので、Parametersブロックに定義して渡すようにしています。

LoadBalancer

https://github.com/awslabs/ecs-refarch-continuous-deployment/blob/master/templates/load-balancer.yaml

LoadBalancerと、紐付けるTargetGroupやSecurityGroupを定義しています。

こちらも、初め利用しようとしていましたが最終的にやめました。
というのも、CFnテンプレートを更新した際に、ELB以外のStackが更新される場合は問題ありませんが、Stackの作り直しやELB Stackの更新が発生すると、ELBのURLが変わってしまいます。

実際にELBをCFnで管理するのであれば、Route53もCFn内で管理して、ELBのエンドポイントに対してCNAMEレコードを作成する必要があると思います。
※ 私が作ったものは社内利用のみだったため、固定されたURLさえあればよく、ELBを固定してパラメータでStackに与えることで対応しました。

ECS Cluster

https://github.com/awslabs/ecs-refarch-continuous-deployment/blob/master/templates/ecs-cluster.yaml

このあと定義する、ECSのサービスを稼働させるインスタンスや、AutoScalingGroupの定義を行っています。(Fargateになればいらなくなるはずのテンプレートです。)

Resoures.SecurityGroup

デフォルトでは、SecurityGroupIngressのIpProtcolに-1が設定されており、ELBのSecurityGroupに対してすべてのポートが解放されています。
http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/APIReference/API_AuthorizeSecurityGroupIngress.html
私は、明示的にポート解放したかったことと、有事の際にsshでインスタンスにログインして調査を行うために、下記の通り変更しました。

SecurityGroupIngress:
  # hostポートが動的なため、ELBに対してエフェメラル開放
  - SourceSecurityGroupId: !Ref SourceSecurityGroup
    IpProtocol: TCP
    FromPort: 1024
    ToPort: 65535
  # ClusterSshBastionSecurityGroupは踏み台サーバーのSecurityGroup
  - SourceSecurityGroupId: !Ref ClusterSshBastionSecurityGroup
    IpProtocol: TCP
    FromPort: 22
    ToPort: 22

Service

https://github.com/awslabs/ecs-refarch-continuous-deployment/blob/master/templates/service.yaml

ECSのサービス、タスクを定義しています。実際のアプリケーション周りの設定はここで行います。
例えば、コンテナで設定したい環境変数はEnvironmentブロックで定義します。

containerのログをCloudWatchに送る

こちらのスニペットにある通りですが、下記の設定を行うことでコンテナのログをCloudWatchへ送ることができます。

Resources:
  CloudWatchLogs:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub ${AWS::StackName}
      RetentionInDays: 14 # 2週間

  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: !Sub ${AWS::StackName}
      ContainerDefinitions:
        - LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Sub ${AWS::StackName}
              awslogs-region: ap-northeast-1
              awslogs-stream-prefix: hogehoge
    # その他propertyは一旦省略

LogGroupはなんでもよいですが、私はStackNameにしました。既存のものを流用するのであれば、上記のCloudWatchLogsのブロックは不要です。

DeploymentPipeline

https://github.com/awslabs/ecs-refarch-continuous-deployment/blob/master/templates/deployment-pipeline.yaml

今回の肝になる部分です。
Github -> CodePipeline -> CodeBuild -> ECS
の流れを自動化するCFn stackをつくります。

概ねサンプルのままで動きますが、ハマりどころが多かったです。

Pipelineブロックのイメージ

私が当時ほとんどCodePipelineを使ったことがなかったためでもありますが、templateだけ見ているとイメージがかなり掴みづらかったので、少し解説します。

1. Pipeline.Stages[0](Name: Source)

Githubからソースをクローンしてきます。
そして、ArtifactStoreで定義したLocationに{なにかのhash値}.zip(※)という名前で保存します。

正直、このサンプル(というかCFnの仕様?)の一番意味がわからないところです。

このName: SourceのOutputArtifactsはAppで、Name: BuildのStage(CodeBuild)に対してInputArtifactとしてAppを渡しています。
awslabsのyamlだと、そのInputArtifactを無視して、別ブロックで定義されたCodeBuildProjectsに記載の通りArtifactとしてBucketを直参照した上で、${ArtifactBucket}/source.zipを参照してCodeBuildを実行しています。
でも、source.zipなんてS3にはないんです、、、

CodeBuildのドキュメントには、

  • source-location: 必須値 (CODEPIPELINE に source-type を設定しない場合)。指定されたリポジトリタイプのソースコードの場所。

    • Amazon S3 では、ビルド入力バケット名の後に、スラッシュ (/) が続き、ソースコードとビルド仕様 (例: bucket-name/object-name.zip) を含む ZIP ファイルの名前が続きます。これは ZIP ファイルがビルド入力バケットのルートにあることを前提としています。(ZIP ファイルがバケット内のフォルダにある場合は、代わりに bucket-name/path/to/object-name.zip を使用してください)。

と記載されていますが、object-nameってなんやねん:joy:
でもこのままでうまく動きます。source.zip以外にしてみたことはないので、どうなってるのかはよくわかりません、、

2. Pipeline.Stages[1](Name: Build)

githubから取得したソースを元に、CodeBuildProjectブロックに記載の通りにdocker buildを実行します。そしてできあがったコンテナをECRにpushします。

はまりどころというわけでもないですが、私が一瞬こんがらがった点を少し。冷静になってみると当たり前なんですが、CodeBuildProjectのEnvironmentブロックは、docker buildを行うdockerコンテナ上の環境変数です。dockerfile内で使えるENVや、最終的なdockerコンテナ(タスク)に渡される環境変数ではありません。Dockerfileで外から変数を受取りたい場合、build.commandsのdockerコマンドに--build-args HOGE=hogeなどとargsを渡してください。

3. Pipeline.Stages[2](Name: Deploy)

ECRのリポジトリがタグ付きで更新されているので、ConfigurationにそのURIなど含めることでいい感じにタスク定義が更新され、service.ymlで定義したスタックが更新されます。(多分)

多分、と書いたのは、実は私は別の方法をとっています。
ここのConfigurationの記述、探したかぎりドキュメントが見つからず、、

- Name: Deploy
  Actions:
    - Name: Deploy
      ActionTypeId:
        Category: Deploy
        Owner: AWS
        Version: 1
        Provider: ECS
      Configuration:
        ClusterName: !Ref Cluster
        ServiceName: !Ref Service
        FileName: images.json
      InputArtifacts:
        - Name: BuildOutput
      RunOrder: 1

私は複数のコンテナをTaskDefinitionに含めており、1つのURIでは対応できませんでした。images.jsonをいい感じに書き換えればうまくいくのか、、わからなかったため、下記の方法をとりました。

複数コンテナを含むタスクをCodePipelineからアップデートする

やったことは、service.yamlをdeployment-pipeline.yamlにネストさせて、DeployアクションでStackの更新を行うようにしました。

- Name: Deploy
  Actions:
    - Name: Deploy
      ActionTypeId:
        Category: Deploy
        Owner: AWS
        Version: 1
        Provider: CloudFormation
      Configuration:
        ChangeSetName: Deploy
        ActionMode: CREATE_UPDATE
        StackName: !Sub "${AWS::StackName}-Service"
        Capabilities: CAPABILITY_NAMED_IAM
        # githubからcloneしたファイルをパス指定
        TemplatePath: App::cfn_templates/packaged_service.yml
        RoleArn: !GetAtt CloudFormationExecutionRole.Arn
        ParameterOverrides: !Sub |
          {
            "Tag" : { "Fn::GetParam" : [ "BuildOutput", "build.json", "tag" ] },
            "DesiredCount": "${TaskDesiredCount}",
            "Cluster": "${Cluster}",
            "TargetGroup": "${TargetGroup}",
            "EcrRepositoryPrefix": "${EcrRepositoryPrefix}",
            "Environment": "${Environment}",
            "S3Bucket": "${S3Bucket}"
          }
      InputArtifacts:
        - Name: App
        - Name: BuildOutput
      RunOrder: 1

軽く解説を。

  • 事前にservice.yamlはaws cloudformation packageコマンドでpackageしておきます。
  • テンプレートはgithubから取得したリポジトリに含んでいるため、InputArtifact経由でのパス指定を行います。
  • ECR Repositoryのprefixと、buildした際のタグをservice.yamlにパラメータとして与えます
    • service.yaml側では下記のような形で、タグを含むURIを指定しておきます。
TaskDefinition:
  Type: AWS::ECS::TaskDefinition
  Properties:
    Family: !Sub ${AWS::StackName}
    ContainerDefinitions:
      - Name: hoge
        Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EcrRepositoryPrefix}/hoge:${Tag}
            - Name: fuga
        Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EcrRepositoryPrefix}/fuga:${Tag}

これで、CodePipelineからCloudFormationのCREATE_UPDATEが行われ、めでたくTaskDefinitionの更新->Serviceの更新という順でDeployが行われます。

はまりどころとしては、ParameterOverridesのブロックが、1000文字しか使えません。もろもろ変数展開されたあとだと、結構簡単に1000文字超えて、

Please provide a valid JSON with maximum length of 1000 characters.

って言われます。
Forumにこんなissueがある程度で、(当時)ドキュメントは見つかりませんでした。

私は悩んだ末、はじめParameterOverridesに書いていた値を、yaml_vaultを使ってリポジトリ内で暗号化して管理するようにしました。

おわりに

すごくながくなりました。疲れました。
細かい所見直せてないかもしれませんが、クリスマスイブにこれ以上は悔しいので一度公開します。

このあたりは後日編集されるかもしれません。。

いろいろと個人的な感情が入っていますが、CloudFormationで環境作ってみたいなーという方の参考になれば幸いです。
えっ、Deploy周りはそんなことしないでCircle CI使えって?あーあー聞こえないー。
(一応今回はAWS内で完結することを目標にしてこの形に落ち着いています)

続きを読む

CloudWatchLogsを使用する

EC2(Linux)上のログをCloudWatchLogsへ連携する

1.対象EC2のIAMにポリシー追加

policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "logs:DescribeLogStreams"
            ],
            "Resource": [
                "arn:aws:logs:*:*:*"
            ]
        }
    ]
}

2.Linuxサーバで次のシェルを実行

※リージョンの指定とawslogs.confの作成部分を編集して下さい
LaunchConfigurations.sh
#リージョン指定
REGION_NAME='ap-northeast-1'

mkdir /etc/awslogs/
cd /etc/awslogs
curl https://s3.amazonaws.com/aws-cloudwatch/downloads/latest/awslogs-agent-setup.py -O

cat << 'EOW' >/etc/awslogs/awslogs.conf

# 下記URLの公式ドキュメント見てawslogs.confを作成してください
#http://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/AgentReference.html
[general]
state_file = /var/awslogs/state/agent-state

[file_name_1]
file = 
log_group_name = 
log_stream_name = 
#datetime_format = #指定しない場合は、自動で読み取ってくれる
#multi_line_start_pattern = {datetime_format} #トレースが出力されるログの場合は必要

[file_name_2]
file = 
log_group_name = 
log_stream_name = 

EOW

chmod +x /etc/awslogs/awslogs-agent-setup.py
chmod +x /etc/awslogs/awslogs.conf
./awslogs-agent-setup.py -n -r ${REGION_NAME} -c awslogs.conf
chkconfig awslogs on

AWSコンソールから確認すると2.で指定したロググループとログストリームが作成されているので、以下の様な事ができます。

・連携したログを監視(ERRORが出たらメール通知など)する
・障害調査に使用する

続きを読む

AWS FargateでサーバーレスZabbix

cloudpack大阪の佐々木です。

概要

Zabbixを使うとなると、サーバの面倒はどうするんだ?って話に絶対なるので、AWS Fargateで動かして、サーバーレス化してしまおうということです。
(実際はサーバーレスってい言えるかどうか分かりませんが・・・)
実用に耐えうるかは考慮してません。

構成イメージ

Untitled(1).png

IPが変わるのもどうかなーということで、NLB、ALBを使ってホスト名でアクセスできるようにします。
ZabbixサーバはNLBで、ZabbixWebの方はALBで受けるので、サービスを分けています。

手順

VPCまわり、クラスタ、RDS、NLB、ALBの手順は省略します。
CloudWatch Logsのロググループも先に作っておく必要があります。

ファイル構成

. ─┬─ server ─┬─ docker-compose.yml
   │          └─ ecs-params.yml
   │
   └─ web ────┬─ docker-compose.yml
              └─ ecs-params.yml

zabbix-server

コンフィグファイル

server/docker-compose.yml
version: '2'
services:
  zabbix-server:
    image: zabbix/zabbix-server-mysql
    ports:
      - "10051:10051"
    environment:
      DB_SERVER_HOST: <RDSのエンドポイント>
      MYSQL_DATABASE: <RDSに設定したDB名>
      MYSQL_USER: <RDSに設定したユーザー名>
      MYSQL_PASSWORD: <RDSに設定したパスワード>
    logging:
      driver: awslogs
      options:
        awslogs-group: <ロググループ名>
        awslogs-region: us-east-1
        awslogs-stream-prefix: zabbix
server/ecs-params.yml
version: 1
task_definition:
  ecs_network_mode: awsvpc
  task_execution_role: ecsTaskExecutionRole
  task_size:
    cpu_limit: 256
    mem_limit: 0.5GB
  services:
    zabbix-server:
      essential: true

run_params:
  network_configuration:
    awsvpc_configuration:
      subnets:
        - <サブネットID>
        - <サブネットID>
      security_groups:
        - <セキュリティグループ>
      assign_public_ip: ENABLED

起動

$ cd server
$ ecs-cli compose service up --launch-type FARGATE \
 --target-group-arn <NLBのターゲットグループのARN> \
 --container-name zabbix-server \
 --container-port 10051

zabbix-web

コンフィグファイル

web/docker-compose.yml
version: '2'
services:
  zabbix-web:
    image: zabbix/zabbix-web-apache-mysql
    ports:
      - "80:80"
    environment:
      DB_SERVER_HOST: <RDSのエンドポイント>
      MYSQL_DATABASE: <RDSに設定したDB名>
      MYSQL_USER: <RDSに設定したユーザー名>
      MYSQL_PASSWORD: <RDSに設定したパスワード>
      PHP_TZ: Asia/Tokyo
      ZBX_SERVER_HOST: <zabbix-serverのホスト名(NLBのFQDN)>
      ZBX_SERVER_NAME: <zabbixの管理画面に表示される名前>
      ZBX_SERVER_PORT: 10051
    logging:
      driver: awslogs
      options:
        awslogs-group: <ロググループ名>
        awslogs-region: us-east-1
        awslogs-stream-prefix: zabbix

起動

$ cd web
$ ecs-cli compose service up --launch-type FARGATE \
 --target-group-arn <ALBのターゲットグループのARN> \
 --container-name zabbix-web \
 --container-port 80

接続

ブラウザから http://(ALBのFQDN) にアクセスするとWeb管理画面にログインできます。

image.png

ダッシュボードでのステータスも正常です。

image.png

エージェントで指定する Server はNLBのFQDNになります。

まとめ

Zabbixは単純にスケールアウトできるものでもないですので、あまりFargateに適したものではないですが、サーバのメンテは誰がやる?って話が進まないようなときは、とりあえずFargate化してしまえばいいんではないでしょうか?

続きを読む

AWS Fargateの機能制限とかできないこととか

cloudpack大阪の佐々木です。

既存タスク定義をFargateに持っていこうとすると、機能が未サポートだったり、制限等によりエラーになったりするかと思います。
Fargateでできないこと、制限などは下記のドキュメントに記載されていました。
http://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html

まだ日本語化されておらず、Fargate専用ページになっていないので、この中からFargateの制限に関する部分を抜き出してみました。
今後変更されると思いますが、現時点(2017/12現在)のものをまとめておきます。
間違いとかあれば指摘ください!

ネットワークモード

If using the Fargate launch type, the awsvpc network mode is required.

awsvpcのみサポート

Task Size

  • Task memory (GB)
  • Task CPU (vCPU)

If using the Fargate launch type, this field is required and you must use one of the following values, which determines your range of valid values for the memory parameter.

タスク全体に割り与えるメモリとCPUで、Fargateでは必須パラメータ
値は組み合わせから選ぶ必要がある

Memory CPU
0.5GB, 1GB, 2GB 0.25 vCPU
1GB, 2GB, 3GB, 4GB 0.5 vCPU
2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB 1 vCPU
4GB 〜 16GB 2 vCPU
8GB 〜 30GB 4 vCPU

コンテナの定義

イメージ

The Fargate launch type only supports images in Amazon ECR or public repositories in Docker Hub.

ECRとDocker Hubのパブリックリポジトリのみサポート

メモリ制限

If your containers will be part of a task using the Fargate launch type, this field is optional and the only requirement is that the total amount of memory reserved for all containers within a task be lower than the task memory value.

コンテナレベルのメモリ制限はオプション
タスクレベルの設定より小さい値にする必要がある

ポートマッピング

containerPort
If using containers in a task with the Fargate, exposed ports should be specified using containerPort.

hostPort
If using containers in a task with the Fargate, the hostPort can either be left blank or needs to be the same value as the containerPort.

コンテナとホストで別ポートにすることはできない

CPUユニット数

This field is optional for tasks using the Fargate launch type, and the only requirement is that the total amount of CPU reserved for all containers within a task be lower than the task-level cpu value.

コンテナレベルのCPUユニット数設定はオプション
タスクレベルより小さい値にする必要がある

リンク

Only supported if the network mode of a task definition is set to bridge.

awsvpc のみサポートのFargateでは使えない
リンク機能の代替については 別記事 に書きました。

ログ設定

If using the Fargate launch type, the only supported value is awslogs. For more information on using the awslogs log driver in task definitions to send your container logs to CloudWatch Logs, see Using the awslogs Log Driver.

ログドライバは awslogs のみサポート

特権付与(privileged)

This parameter is not supported for Windows containers or tasks using the Fargate launch type.

未サポート

Dockerセキュリティオプション

This parameter is not supported for Windows containers or tasks using the Fargate launch type.

未サポート

linuxParameters

This parameter is not supported for Windows containers or tasks using the Fargate launch type.

未サポート

制約(Task Placement Constraints)

If you are using the Fargate launch type, task placement contraints are not supported.

インスタンスが見えないので当然未サポート

ボリューム

If you are using the Fargate launch type, the sourcePath parameter is not supported.

ソースパスが未サポートなのでホストのファイルシステムをマウントすることはできない
これができなにので、Datadogとかのエージェントコンテナを使って監視するような構成ができないのではないかと思います。
CloudwatchでCPUとメモリの値はとれますが、こういう外部ツールとの連携もできればいいなとは思います。

その他

ALB、NLBと連携はできますが、Fargateのタスクを落としても、LBは削除されません。
当たり前なんですが、実際使ってみると、インスタンスの存在は気にせずに使えるのに、LBは意識しないといけないというのが違和感・・・
課金のことを考えると停止もできないので、都度削除とか、このへんも改善してほしいと思いました。

続きを読む

Fargate の監視についての考察

この記事は AWS Fargate Advent Calendar 2017 の16日目の記事です。

概要

AWS Fargate の登場により、EC2インスタンスやそのクラスタを意識せずともコンテナが実行できるようになりました。AWSさん曰く、”コンテナをデプロイする最も簡単な方法”ということです。すごいですね。一方で、コンテナを本番環境に導入すると”9ヶ月でコンテナ数が5倍に増加する”や”仮想マシンより9倍早く縮退するようなライフサイクルで利用される”といったレポート1 もありますので、より流動的になるインフラに対して運用時の可視性を確保するためにFargateのモニタリングについて考えてみました。

AWS Fargate で利用可能なモニタリング

AWS FargateはこれまでのAmazon ECSと同様にCloudWatch Logs と CloudWatch Eventsをサポートしているので、awslogsを通じてアプリケーションやミドルウェアのログはCloudWatch Logsに転送することが可能で、Fargate タスクの状態変化はCloudWatch EventsのトリガとしてSNSトピックやLambda関数を実行できます。
CloudWatch メトリクスは非常にシンプルで、ECSではクラスタ単位のメトリクスをモニタしていましたが、それがサービス単位のCPUとメモリのメトリクスのみになります。

Fargate 運用の可視性を確保するための要素

システム上で起こりうるあらゆる事象に対して可視性を確保するうえで、必要なデータを 5W1H的な分類で整理してみると以下のようになります。WHATはグラフ化できるような数値データ、WHYはログや状態を示すイベント、HOWはアプリケーションのトランザクショントレース(APM, Application Performance Monitoring)として、WHENとWHEREについては基本的にモニタリングのデータは時系列データでありタグ等のメタデータが付属するため共通するものなので割愛してます。( 例えば、この分類で車の運転のモニタリングを考えると、WHATは速度などの計器類のデータ、WHYはドライバーの操作ログ、HOWはドライブレコーダー、という感じです。どれが欠けても事故の調査は難しくなるし、全部無い=モニタリングしない、を想像すると恐ろしいですねw )

Fargateの
モニタリング
WHAT WHY HOW
データのタイプ メトリクス ログ トレース
対応サービス CloudWatch
(メトリクス)
CloudWatch
Logs, Events
(なし)
データの種類 CPU, mem アプリ, ミドル (なし)
データの粒度 サービス単位 サービス単位,
タスク単位
(なし)

さてこうしてみると、Fargateがコンテナ環境のモニタリングにおいてトレースをサポートできていないのは痛いなぁと思うのと同時に、AWSのことだから割とすぐにX-RayがFargateをサポートするんだろなー…とか想像できます。2
また、ログのサポートが整っている一方で、メトリクスはCPUとメモリだけでディスクI/OやネットワークI/Oが無い、サービス単位だけでタスク単位では見られない、などまだ手薄感がありますが、こちらは今時点はECSのモニタリングの仕組みを流用してるからかなー、Docker Stats API のFargate版みたいなのすぐ出ますよねぇ?とか思うわけです。3

まとめ:Fargateはどうモニタリングするか

モニタリングのプロセスを分けて考えると、データ収集と保存–>可視化–>アラート–>分析–>ナレッジの蓄積、という感じになると思いますが、各プロセスについて以下のように対応を進めるのが良いかと思っています。

Fargateの
モニタリングプロセス
対応サービス メモ
データ収集と保存 CloudWatch,
Events, Logs
ログも忘れず収集する
可視化 CloudWatch
Dashboard
意外と使われていない気がするけど
便利なので使ったほうがいいと思う
アラート CloudWatch Eventsも併用する
分析 CloudWatch
Dashboard
作り込めば問題分析に使えるので
やはり使ったほうがいいと思う
ノウハウの蓄積 (なし) ポストモーテム用にDashboardの時間が
固定できればいいのになぁ

BIツールやELKスタックを利用することがかなり普及していて重要なデータを可視化することが良いとは良く知られていると思いきや、モニタリングデータの可視化はそれほど重要視されていない気がしてやった人のみぞ知る、、、となっている気がするので、手近なところでCloudWatch Dashboardは取り組んで見る価値が十分あるんじゃないかなと思います。4



  1. 「Docker採用の驚くべき8つの事実」 Datadogが毎年実施しているユーザー調査レポートです。コンテナのライフサイクルの短縮は毎年進んでいます。 

  2. これについては、本当に何も知らず想像で言っていますが、、多分そうなるでしょう。 

  3. Datadogは Fargateのローンチパートナーで3rdパーティのモニタリングツールとしてFargateのリリース時に紹介されていますが、実際このAPIが無いとdocker-dd-agentがうまく仕事出来ないので困るのです。このAPIが出た際は記事を更新します。 

  4. 可視化含めてモニタリングプロセスの劇的な改善をするのがDatadogの強みなのでご興味ある方は“無償トライアル”へGo!!,,,ご清聴ありがとうございました。 

続きを読む