AWS VPCの勉強メモ

試しに下記の環境をCloudFormationを使って構築してみる。

Picture1.png

下記、作成したCloudFormationのテンプレート。
https://github.com/khiraiwa/cloud-certifications/blob/1ab33227b014a4dc0679291729b1a117420a5cde/vpc/template.yml

YAML形式でやってみた。これだとコメントが書ける。

################################
#### VPC
################################
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock:
        Ref: VpcCidrBlock
      EnableDnsSupport: 'true'
      EnableDnsHostnames: 'true'
      Tags:
      - Key: Name
        Value:
          Ref: VpcName

また、最近だと短縮記法も使えるようになっている。

  SubnetPublic1a:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: VPC
      AvailabilityZone: !Sub
      - "${AWS::Region}${AZ}"
      - AZ: !Select [0, !Ref VpcAzs]
      CidrBlock:
        Ref: VpcSubnetCidrBlockPublic1a
      MapPublicIpOnLaunch: true
      Tags:
      - Key: Name
        Value:
          Ref: VpcSubnetNamePublic1a

SubnetにセットするNetwork ACLにはデフォルトで*のルールが有る。
追加したルールに一致するものがなければ*のルールが使われる。
(つまり、拒否される)

VPC_Management_Console 2.png

参照: http://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/VPC_ACLs.html#ACLRules

また、Network Aclはステートレス。例えばインバウンドでtcp/22を許可していいてもアウトバウンドは通さない。戻りのローカルポート(1024-65535とか)をアウトバウンドで開ける必要がある。
(逆にSecurityGroupはステートフル)

また、下記の図のように、あるサブネットからは特定のサブネットにしか通信できないようにするにはNetwork ACLで制御する(しかないのかな?)

Picture2.png

VPCペアリングはペアリングしたVPCにしか通信できない。例えば下記の場合、AからCには行けない。

Picture4.png

続きを読む

AWS LambdaでImageMagickによる画像への文字埋め込みで好きなフォントを使う方法

LambdaでImageMagickを用いて好きなフォントを使う方法

方法は単純。
TrueTypeFontファイルへのパスをfontに渡してあげるだけ。
なのでデプロイパッケージに好きなフォントのttfファイルを含めてあげればできる。
下にサンプルソース。
(imagemagickのコマンドを使用する、こっちのほうが個人的には楽だった)

const exec = require('child_process').exec;
const aws = require('aws-sdk');
const s3 = new aws.S3({region: "ap-northeast-1"});
const fs = require ('fs');
const uuid = require('uuid');

exports.handler = (event, context, callback) => {
  const dir = `/tmp/${uuid.v4()}`; // 書き込み可能なのは/tmp以下だけ、かつ名前重複でエラー起きるので重複できないようにする。
  const options = [
    "-size 300x300", // 画像のサイズ。変更元をxc:whiteにしているため必要
    "-font ipam.ttf", // TrueTypeFontファイルへのパス。好きなフォントのTTFを使えばよろし。
    "-pointsize 24", // 文字サイズ
    '-fill "#000000"', // 文字色
    "-gravity center", // 文字の基準位置
    '-annotate +0+0 "テスト"' // 文字。基準位置(もしくは左上)からの位置を調整できる。
  ];
  exec(`mkdir ${dir} && convert ${options.join(" ")} xc:white ${dir}/image.png`, (err, stdout, stderr) => {
    if (err) {
      callback(stderr.split("n"));
      return;
    }
    s3.putObject({
      Bucket: "",
      Key: "",
      Body: fs.createReadStream(`${dir}/image.png`),
      ContentEncoded: 'base64',
      ContentType: 'image/png'
    }, (ex) => {
      if (ex) {
        callback(ex);
        return;
      }
      callback(null, "success");
    });
  });
};

続きを読む

AWS Lambda と Fastly instant purge を利用して S3 コンテンツの更新を即時反映する

Fastly CDN の特徴のひとつに、Instant purge(コンテンツの即時削除)があります。通常、CDN では更新の多いコンテンツはキャッシュすることができませんが、Fastly では Purge を即座に行うことが可能であるため、更新の多いコンテンツでもキャッシュすることが可能です。
ただし、コンテンツが変更されるたびに Instant purge を手動で行うのは理想的な運用とはいえません。ここでは AWS Lambda を利用して、S3 でのファイル更新を検知して、自動的に Instant purge を実行する方法を紹介します。

Lambda の設定

  1. Services から Lambda を選択し、Create a Lambda function をクリックします
  2. blueprint が表示されますが、ここでは Blank Function を選択します
  3. トリガーの設定画面が表示されるので、S3 を選択し、Bucket にコンテンツが配置される S3 バケットを選択し、Event type を PUT とします
    Screen Shot 0029-07-18 at 21.06.54.png
  4. Name / Description を入力し、Runtime はデフォルトの Node.js を選択します。
    Screen Shot 0029-07-18 at 21.11.26.png
  5. Code は下記のように設定します。hostname を Instant purge を行うドメインに変更してください。
    下記のコードでは、アップデートされたパスを key に格納し、options にドメイン、ポート番号、method(purge のため、PURGE) と共に設定し、http.request で Instant purge のリクエストを送信しています。
console.log('Loading event');
var aws = require('aws-sdk');
var s3 = new aws.S3({apiVersion: '2006-03-01'});
var http = require('http');

exports.handler = function(event, context) {
   console.log('Received event:');
   console.log(JSON.stringify(event, null, '  '));
   // Get the object from the event and show its content type
   const bucket = event.Records[0].s3.bucket.name;
   const key = event.Records[0].s3.object.key;
   s3.getObject({Bucket:bucket, Key:key},
      function(err,data) {
        if (err) {
           console.log('error getting object ' + key + ' from bucket ' + bucket + 
               '. Make sure they exist and your bucket is in the same region as this function.');
           context.done('error','error getting file'+err);
        }
        else {
           console.log('CONTENT TYPE:' + data.ContentType + ' bucket: ' + bucket + ' path: ' + key);
           context.done(null,'');
        }
      }
   );

  var options = {
    hostname: 'www.example.com',
    port: 80,
    path: '/' + key,
    method: 'PURGE',
  };

  callback = function(response) {
  var str = '';

    //another chunk of data has been recieved, so append it to `str`
    response.on('data', function (chunk) {
      str += chunk;
    });

    //the whole response has been recieved, so we just print it out here
    response.on('end', function () {
      console.log(options.hostname + ':' + options.port + options.path + ' method:' + options.method);
      console.log(str);
    });
  }

  http.request(options, callback).end();
};

6 . Role は Choose an existing role を選択し、Existing role を選択します。
Screen Shot 0029-07-18 at 21.14.43.png

以上で設定は完了です。

動作確認

ここでは index.html ファイルを更新し、パージによりキャッシュ MISS となることを確認します。
1. curl コマンドでキャッシュヒットになることを確認します。

$ curl -svo /dev/null http://www.example.com/lambda
...
< Age: 3
...
< X-Cache: MISS, HIT
< X-Cache-Hits: 0, 1

2 . s3 上の lambda ファイルを更新します
3. curl コマンドでキャッシュミスになることを確認します。

$ curl -svo /dev/null http://www.example.com/lambda
...
< Age: 0
...
< X-Cache: MISS, MISS
< X-Cache-Hits: 0, 0

4 . CloudWatch のログでも下記のように PURGE リクエストが送信されていることが確認できます。
Screen_Shot_0029-07-18_at_22_40_08.png

続きを読む

TerraformでAWSの構成を読み取るぞ(Terraform import)

前書き

Terraform import については、Qiitaでお2人も記載してくれていますので、参照ください。

tyasuさん:terraform importの使い方メモ
kt_higaさん:terraform importを試してみた

同じスタンスで記載してもしょうがないので、勉強している最中に違う観点でいくつかTerraform import について記載します。

環境

Windows にTerraform(2017/7/18時点:v0.9.11)をインストール
AWSには、EC2,ELB,RDS,S3 など以前手作業で作った環境があります。

準備

まずは、最低限Terraformの利用には、access_key, secret_keyが必要です。
こちらを参照してキーを確認しましょう。
Windowsの作業フォルダにファイルを作成して、確認したキーを記載します。
※ ファイル名は、<任意の名前>.tf としてください。
ここでは、c:work フォルダを作業フォルダとします。

provider "aws" {                                            ・・・・決め打ちです。
    region      = "ap-northeast-1"                          ・・・・リージョンを記載(ap-notheast-1:日本リージョン)
    access_key  = "XXXXXXXXXXXXXXXXX"                       ・・・・access_keyを登録
    secret_key  = "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" ・・・・secret_keyを登録
}

※ ほとんどが決め打ちです。
   今回は、AWS環境のため"aws" となります。

インポート!

叩くコマンドは、簡単!

AWSのマネジメントコンソールから、インスタンスID(赤枠部分)を確認して、コマンドのオプションに指定します。
C:> cd work
C:work> terraform import aws_instance.<任意の名前> <インスタンスID>

題.png

ここでは、こんな感じでコマンドを叩いてみました。
もともとの環境では、タグ名を「batchserver01」という名前を付けていましたが、ここではわざと「batch01」に変更しています。
タグ名と合わせても勿論OKです。

import02.png

無事、terraform.tfstate ファイルが作成されました。

※ <> 部分は、修正しています。

{
    "version": 3,
    "terraform_version": "0.9.11",
    "serial": 0,
    "lineage": "XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {
                "aws_instance.batch01": {
                    "type": "aws_instance",
                    "depends_on": [],
                    "primary": {
                        "id": "<INSTANCE_ID>",
                        "attributes": {
                            "ami": "<AMI_ID>",
                            "associate_public_ip_address": "false",
                            "availability_zone": "<AvailabilityZone_ID>",
                            "disable_api_termination": "false",
                            "ebs_block_device.#": "0",
                            "ebs_optimized": "false",
                            "ephemeral_block_device.#": "0",
                            "iam_instance_profile": "",
                            "id": "<INSTANCE ID>",
                            "instance_state": "running",
                            "instance_type": "<INSTANCE_TYPE>",
                            "ipv6_addresses.#": "0",
                            "key_name": "<KEY_NAME>",
                            "monitoring": "false",
                            "network_interface.#": "0",
                            "network_interface_id": "<NW_INTERFACE_ID>",
                            "primary_network_interface_id": "<NW_INTERFACE_ID>",
                            "private_dns": "<DNS_NAME>",
                            "private_ip": "<PRIVATE_IP>",
                            "public_dns": "",
                            "public_ip": "",
                            "root_block_device.#": "1",
                            "root_block_device.0.delete_on_termination": "false",
                            "root_block_device.0.iops": "100",
                            "root_block_device.0.volume_size": "10",
                            "root_block_device.0.volume_type": "gp2",
                            "security_groups.#": "0",
                            "source_dest_check": "true",
                            "subnet_id": "<SUBNET_ID>",
                            "tags.%": "1",
                            "tags.Name": "batchserver01",
                            "tenancy": "default",
                            "volume_tags.%": "0",
                            "vpc_security_group_ids.#": "1",
                            "vpc_security_group_ids.2148762998": "<SECURITY_GROUP_ID>"
                        },
                        "meta": {
                            "schema_version": "1"
                        },
                        "tainted": false
                    },
                    "deposed": [],
                    "provider": "aws"
                }
            },
            "depends_on": []
        }
    ]
}

ここから本題

★ EC2を1台しかimportしていないので、もう1台importしたらどうなるんだ?

import01.png

別に問題なくコマンド終了。
先ほど作成されたterraform.tfstate は、terraform.tfstate.backup にCOPYされ、terraform.tfstate に2台目が追記されました。
(省略)

★ S3をimport しても大丈夫だよね?

C:work> terraform import aws_s3_bucket.<任意の名前> <バケット名>

わざとバケット名を間違えて見ました。

import03.png

★ ALBをimport !!

C:work> terraform import aws_alb.<任意の名前> <ARN>

import04.png

結果、terraform.tfstate ファイルにはどんどんimport すればしただけ、追記されていきました。
どんどん下に追記されていくわけではなく、DataSource(aws_s3_bucket, aws_instance, aws_albなど)の名前で順に並んでいくようです。

参考サイト

対応しているDataSource をはじめ、オプション確認にはやっぱりここは外せません。

本家HashiCorp:AWS Provider

続きを読む

API Gatewayを通じてKinesisにPOSTでデータを入れる方法

API GatewayにPOSTで送ったリクエストを直接Kinesisに入れる方法について解説します。

「あるURLにPOSTメソッドでデータを送ると、Kinesisにそのデータが入れることが可能な仕組み」

を作ることができます。

Kinesisにデータを入れる以外にも、参照/削除もできます。

以下の作業はすべて、AWSのコンソール画面から行うことを想定しています。

目次

  • IAMでロールの作成
  • Kinesis Streamsの作成
  • API Gatewayの設定
  • 確認
  • エラー集
  • 参考

目次としては、以上となります。AWSの3つのサービスを使用します。

IAMでロールの作成

本システムでは、「API Gateway」で設定するロールを一つ用意する必要があります。

IAM上で「AmazonKinesisFullAccess」ポリシーをもったロールを作成すれば大丈夫です。
適当に、KinesisProxyRoleと呼ぶことにします。

Kinesis Streamsの作成

続いて、Kinesisストリームを用意します。
AWSコンソール上で、「Kinesisストリームの作成」をクリックすると、以下の画面が表示されます。

スクリーンショット 2017-07-18 17.28.03.png

ストリームの名前をつけましょう。test_streamと呼ぶことにします。
この名前は慎重につけましょう。理由としては、このストリーム名がAPI GatewayのURLパターンで参照されるからです。

API Gatewayの設定

API Gatewayの管理画面を開きます。

APIの作成

[APIの作成]より、「新しいAPI」を選択し、API名を入力します。

スクリーンショット 2017-07-18 17.33.31.png

作るものを確認

さて、これからどういうAPIを作っていくかですが、次のようなURLに対してPOSTで送れるものを作ります。

http://<api gateway url>/<DeployされるStage名>/streams/<Stream名>/record

/streamsの作成

これは、コンソール画面の[アクション] -> [リソースの作成]より、入力します。

入力内容
リソース名 Streams
リソースパス streams

ここでは特にメソッドを作成しなくて大丈夫です。

/streams/{stream-name}の作成

/streams配下に、以下のように入力します。

入力内容
リソース名 stream-name
リソースパス {stream-name}

この波括弧ですが、stream_nameというなめのパスパラメータを表しており、この部分に、先ほど作ったKinesisの名前を入れます。

このリソース配下でも、特にメソッドを作成しなくて大丈夫です。

/streams/{stream-name}/recordの作成

/streams/{stream-name}配下に、以下のように入力します。

入力内容
リソース名 Record
リソースパス record

このリソースでは[メソッドの作成]より、POSTメソッドを作成します。
次に、作成された[POST]という部分をクリックします。
表示された、図中の「統合リクエスト」をクリックします。

入力内容
統合タイプ サービス
AWS リージョン Kinesisと同じregion
AWS サービス Kinesis
AWS サブドメイン (空白)
HTTP メソッド POST
アクション PutRecord
実行ロール arn:aws:iam:123456xxxx:role/KinesisProxyRole

と設定します。

続いて同じ画面において、
[HTTP ヘッダー]で

名前 マッピング元
Content-Type ‘x-amz-json-1.1’

[本文マッピングテンプレート]で、「テンプレートが定義されていない場合」を選び、[マッピングテンプレートの追加]を押します。
Content-Typeとして、application/json と入力します。

そして、テンプレートの部分に以下をPasteしてください。

{
    "StreamName": "$input.params('stream-name')",
    "Data": "$util.base64Encode($input.json('$.Data'))",
    "PartitionKey": "$input.path('$.PartitionKey')"
}

以上で、設定は完了です。

ここまでの設定を確認するために、[リソース] -> [/recordのPOST]をクリックします。
表示された図中の[テスト]をクリックすると、

指定された入力で メソッドに対してテストコールを行います

という画面に遷移します。

{stream-name}に、test_streamを入力し、リクエスト本文に、

{
   "Data": "some data",
   "PartitionKey": "some key"
}

を貼ります。 [テスト]をクリックしましょう。

すると、

リクエスト: /streams/test_stream/record
ステータス: 200
レイテンシー: 121 ms

レスポンス本文

{
  "SequenceNumber": "1234.......................",
  "ShardId": "shardId-000000000000"
}

という形で、成功が確認できます。

CORSの設定が必要であれば、[アクション]より「CORSの有効化」を押せば設定されます。

確認するために、このAPIをデプロイしましょう。
[アクション] -> [APIのデプロイ]より、ステージを作成しましょう。今回は prodとしましょう。

確認

今回deployにより取得したURLは以下のようになります。

https://hogehoge.fugafuga.amazonaws.com/prod/streams/test_stream/record

こちらをcurlで叩いて見る場合は、以下のコマンドで叩けます。

$ curl -X POST https://hogehoge.fugafuga.amazonaws.com/prod/streams/test_stream/record -H 'Content-type: application/json'  -d '{"Data": "some data","PartitionKey": "some key"}'

JqueryのAjaxで送信する場合は以下のようにします。

js
var data = {"Data": "some data", "PartitionKey": "some key"};
$.ajax({
    type:"POST",
    url:"https://hogehoge.fugafuga.amazonaws.com/prod/streams/test_stream/record",
    data: JSON.stringify(data), // stringifyはお忘れなく
    scriptCharset: 'utf-8',
    contentType: 'application/json',
    success: function(data) {
    }
});

エラー集

やってるときにエラーに遭遇しそうなエラーに関する記述です。

[API Deploy時に] No integration defined for method

これは、

「HTTPメソッドを定義してるけど、integrationに関する情報がない」

というエラーです。つまり、GETやPOSTなどのメソッドを追加しているものの、「Integration Request」の情報の入力がされてないという状態です。

解決方法としては、「追加したもののIntegrationに関する情報を登録してないメソッドは削除する」です。

参考

https://forums.aws.amazon.com/thread.jspa?threadID=216710

[KinesisにRequest送信時に] 1 validation error detected: Value ” at ‘partitionKey’ failed to satisfy constraint: Member must have length greater than or equal to 1

これは、

「partitionKeyは1文字以上必要だ」

というエラーです。起こっているのは、おそらく、Kinesisに対する送信パラメータに「PartitionKey」が足りてない、もしくは、認識されてないということでしょう。

よくあるのは、JavaScriptでKinesisにパラメータを送る場合、PartitionKeyを入れ忘れることがあるので注意しましょう。

JavaScriptから送信する場合は、jsonオブジェクトを JSON.stringifyで文字列化しない場合、こういうエラーが出ます

参考

http://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/integrating-api-with-aws-services-kinesis.html

続きを読む

AWSでWOWZAサーバ構築に使用するAMI

探したのでメモ

Version 4.6.0* released 04/18/2017

Region AMI ID
Asia Pacific (Mumbai) ami-ccc9baa3
EU (London) ami-61c5d105
EU (Ireland) ami-34f1f552
Asia Pacific (Seoul) ami-811fcdef
Asia Pacific (Tokyo) ami-a6a689c1
South America (Sao Paulo) ami-6ddbb901
Canada (Central) ami-82d569e6
Asia Pacific (Singapore) ami-de3a83bd
Asia Pacific (Sydney) ami-8a343de9
EU (Frankfurt) ami-c38d50ac
US East (N. Virginia) ami-4cd0405a
US East (Ohio) ami-9c9fbbf9
US West (N. California) ami-6695b006
US West (Oregon) ami-18fd6078

Version 4.5.0 released 06/29/2016

Region AMI ID
Asia Pacific (Mumbai) ami-6d94fe02
EU (Ireland) ami-91960ce2
Asia Pacific (Singapore) ami-8025f7e3
Asia Pacific (Sydney) ami-c391b9a0
Asia Pacific (Seoul) ami-6577bc0b
EU (Frankfurt) ami-62dc370d
Asia Pacific (Tokyo) ami-8e0ffeef
US East (N. Virginia) ami-969f56fb
US East (Ohio) ami-75356e10
US West (N. California) ami-901c5bf0
South America (Sao Paulo) ami-3952c755
US West (Oregon) ami-8b3ff9eb

Version 4.4.1 released 03/18/2016

Region AMI ID
EU (Ireland) ami-5995102a
Asia Pacific (Singapore) ami-6039f203
Asia Pacific (Sydney) ami-8f0b2bec
Asia Pacific (Seoul) ami-08935a66
EU (Frankfurt) ami-0515f26a
Asia Pacific (Tokyo) ami-a0aaa1ce
US East (N. Virginia) ami-68f7f402
US West (N. California) ami-6a5d2f0a
South America (Sao Paulo) ami-6cd95500
US West (Oregon) ami-cb5db4ab

Version 4.3.0 released 11/05/2015

Region AMI ID
EU (Ireland) ami-c0a77eb3
Asia Pacific (Singapore) ami-4566a126
Asia Pacific (Sydney) ami-51520c32
Asia Pacific (Seoul) ami-269d5348
EU (Frankfurt) ami-b52c3fd9
Asia Pacific (Tokyo) ami-ee183f80
US East (N. Virginia) ami-ae473bc4
US West (N. California) ami-a65639c6
South America (Sao Paulo) ami-b148f3dd
US West (Oregon) ami-86e7f0e7

Version 4.2 released 08/10/2015

Region AMI ID
EU (Ireland) ami-d03f74a7
Asia Pacific (Singapore) ami-7c3b392e
Asia Pacific (Sydney) ami-23c48219
EU (Frankfurt) ami-c81b1ed5
Asia Pacific (Tokyo) ami-0652e506
US East (N. Virginia) ami-1370ad78
US West (N. California) ami-1fb4495b
South America (Sao Paulo) ami-9f77f882
US West (Oregon) ami-b1686481

参考:Wowza Streaming Engine 4: Pro Edition (HVM)

続きを読む

AWS ElasticSearch ServiceのログをElastAlertで監視する

ElastAlertをAWSのElasticSearchで使う具体的な方法がネットに転がってなかったのでメモ。

環境は
Ubuntu: 14.04
ElasticSearch: 5.3
ElastAlert: 0.1.16

EC2のIAM Roleに権限付与する

下記のようなポリシーを、ElastAlertを乗せるEC2のIAM Roleに付与する。

Policy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "es:*",
            "Resource": [
                "ElasticSearch ServiceのARN"
            ]
        }
    ]
}

install

pipでインストールする。
python2でないと動作しない。

command
sudo pip2 install elastalert

ElastAlert用のインデックスを生成する

command
elasticsearch_host=hoge.com
aws_region=ap-northeast-1

elastalert-create-index 
  --host ${elasticsearch_host} 
  --port 443 
  --aws-region ${aws_region} 
  --url-prefix '' 
  --no-auth 
  --ssl 
  --verify-certs 
  --index elastalert_status 
  --old-index elastalert_status
response
New index elastalert_status created
Done!

共通設定作成

これから通知のためのルールを作っていくが、それらに共通する設定を記載する。

command
# elastalert用のディレクトリ作成(どこでもよい)
mkdir -p /etc/elastalert
cd /etc/elastalert

vim config.yml
config.yml
es_host: [ESのendpoint]
es_port: 443
use_ssl: True
aws_region: ap-northeast-1
rules_folder: /etc/elastalert/rules
buffer_time:
  hours: 1
run_every:
  minutes: 1
writeback_index: elastalert_status
# Slackを利用する場合(下記のRuleにも記載可)
slack_username_override: "ElastAlert"
slack_webhook_url: "[Slackのwebhook url]"

個別の通知ルール作成

通知をするためのルールを作成する。
ルールの記載方法については下記参照。
http://elastalert.readthedocs.io/en/latest/ruletypes.html
http://elastalert.readthedocs.io/en/latest/recipes/writing_filters.html

command
mkdir -p /etc/elastalert/rules

vim rules/hoge_rules.conf
rules/hoge_rules.conf
name: status200-moniroting
type: frequency
index: access_log*
num_events: 10       # 発生回数
timestamp_field: tu  # Indexでのtime fieldを設定しないとエラーになる
timeframe:
    minutes: 1       # 1分間計測する
filter:
  - term:
      status: 200    # http statusが200なら
alert:
  - slack

実行

command
elastalert --config /etc/elastalert/config.yml

続きを読む

ACMを利用した独自取得SSL証明書更新をAnsibleでやってみる

はじめに

Webサービスを運営しているとSSL証明書の更新作業が発生します。
AWSでELBを利用している場合、以下の2パターンになると思います。

  • 1.ACM(AWS Certificate Manager)でSSL証明書を発行し、ELBにアタッチ
  • 2.取得済みSSL証明書をELBにアタッチ(IAM)

1.ACMでSSL証明書を発行し、ELBにアタッチ

ACMでSSL証明書を発行してELBにアタッチしている場合、更新作業は不要です。以上。

2.取得済みSSL証明書をELBにアタッチ(IAM)

独自に取得したSSL証明書を利用する場合、

  • 1.IAMに新しいSSL証明書をアップロード
  • 2.ELBにアタッチされているSSL証明書を新しい証明書に切り替え
  • 3.上記を繰り返す

というような作業になります。

ACMが使えるのなら話は早いのですが、何らかの事情によりACMでSSL証明書を発行せず独自に取得したSSL証明書を利用するケースは意外と多いのではないでしょうか。

しかし、その場合でもACMを利用することで作業負荷を減らすことができます。

ACMを利用した取得済みSSL証明書更新

ACMを利用するメリットは、アップロードしたSSL証明書を上書きできることです。
ELBにアタッチしたSSL証明書を切り替える必要がないため、ACMに対する作業だけで済みます。

やり方

  • 1.ACMに取得済みSSL証明書をアップロード
  • 2.ELBに上記ACMのSSL証明書をアタッチ
  • 3.ACMのアップロード済みSSL証明書を更新

ACMを利用した取得済みSSL証明書更新(by Ansible)

上記作業をAnsibleでやってみます。

前提

  • AWS CLIが必要です。
  • credential情報は環境変数かaws configureでセットしてある必要があります。
  • ELBへのSSL証明書アタッチ作業はフォローしません。

機能

AnsibleにはACMを操作するモジュールがありませんので、shellモジュールでAWS CLIを実行します。

SSL証明書新規登録/更新

新規登録

  • ARN未指定の場合
  • work/にあるSSL証明書ファイルをアップロード
  • 登録後、work/にアサインされたARNをテキストファイルで保存
  • SSL証明書ごとのリージョン指定に対応(CloudFrontのため)
    • 未指定の場合、my_vars.aws.common.regionで指定したリージョンとなる

更新

  • ARN指定かつyearタグを変更した時
  • yearタグを変更しない限り、更新をスキップする。同一証明書を更新しても実害はないが、アカウントあたり20回/年の制限に引っかかるため
    • アップロードしすぎるとYou have imported the maximum number of 20 certificates in the last year.のようなエラーが出ます。公式ドキュメントにはないような。。。

タグ更新

  • タグのみの更新はできない。新規登録時またはyearタグ変更時、タグも更新される
  • 複数指定可能
  • タグ削除には未対応

ディレクトリ構成

ディレクトリ構成
site.yml
roles/
|--acm/
|  |--tasks/
|  |  |--main.yml
|  |--templates/
|  |  |--save_arn.j2
hosts/aws                  #inventory
host_vars/
|--localhost.yml
work/                      #SSL証明書ファイルを配置しておく
|--example.com-2017.crt        #サーバ証明書
|--example.com-2017.ca-bundle  #中間証明書
|--example.com-2017.key        #サーバ秘密鍵(パスフレーズなし)

inventory

AWSリソース関連モジュールはすべてlocalhostで実行するので、下記のようなインベントリファイルを用意します。

hosts/aws
[aws]
localhost

vars

こんな感じに変数を定義します。今回はhost_varsで定義しました。

host_vars/localhost.yml
---
my_vars:
  aws:
    common:
      region: ap-northeast-1
    acm:
      ssl_crt:
        example_com:
          name: example.com
          # arn: arn:aws:acm:ap-northeast-1:XXXXXXX:certificate/XXXXXXXXXX #アップロード済みの場合ARNを指定する
          tags:
            - key: year
              value: '2017'
            # - key: tag_key   #他のタグをつける場合
            #   value: tag_value
          files:
            crt:
              org_file: example.com-2017.crt
            chain:
              org_file: example.com-2017.ca-bundle
            key:
              org_file: example.com-2017.key
        example_com_cloudfront:
          name: example.com
          region: us-east-1 #CloudFrontはus-east-1固定
          # arn: arn:aws:acm:us-east-1:XXXXXXX:certificate/XXXXXXXXXX #アップロード済みの場合ARNを指定する
          tags:
            - key: year
              value: '2017'
            # - key: tag_key   #他のタグをつける場合
            #   value: tag_value
          files:
            crt:
              org_file: example.com-2017.crt
            chain:
              org_file: example.com-2017.ca-bundle
            key:
              org_file: example.com-2017.key

Role

  • 現在のyearタグの値を取得し(aws acm list-tags-for-certificate)、変数に辞書形式で格納します。yearタグがなければ「N/A」をセットします。
  • 与えられたyearタグの値と比較し、異なっていたら証明書をアップロード(aws acm import-certificate)し、タグをセット(aws acm add-tags-to-certificate)します。
  • aws acm import-certificateの戻り値はARNなので、これをtemplateモジュールでファイルに出力します。
roles/acm/tasks/main.yml
---
- name: tag(year)確認
  shell: >-
    {% if item.value.region is defined %}
    export AWS_DEFAULT_REGION='{{ item.value.region }}' && \
    {% else %}
    export AWS_DEFAULT_REGION='{{ my_vars.aws.common.region }}' && \
    {% endif %}
    aws acm list-tags-for-certificate \
     --certificate-arn {{ item.value.arn }} \
     --query 'Tags[?Key == `year`].Value' \
     --output text
  with_dict: "{{ my_vars.aws.acm.ssl_crt }}"
  when: item.value.arn is defined
  check_mode: no
  changed_when: no
  register: acm_tags

- name: tag(year) dict作成
  set_fact:
    year_tag_dict: >-
      {%- set tmpdict = {} -%}
      {%- for i in range(acm_tags.results|length) -%}
      {%-   if acm_tags.results[i].stdout is undefined -%}
      {%-     set _ = tmpdict.update({acm_tags.results[i].item.value.name: "N/A"}) -%}
      {%-   else -%}
      {%-     set _ = tmpdict.update({acm_tags.results[i].item.value.name: acm_tags.results[i].stdout}) -%}
      {%-   endif -%}
      {%- endfor -%}
      {{ tmpdict }}
  check_mode: no
  changed_when: no

- name: SSL証明書アップロード
  shell: >-
    {% if item.value.region is defined %}
    export AWS_DEFAULT_REGION='{{ item.value.region }}' && \
    {% else %}
    export AWS_DEFAULT_REGION='{{ my_vars.aws.common.region }}' && \
    {% endif %}
    CRT_ARN=$( \
    aws acm import-certificate \
     {% if item.value.arn is defined %}
     --certificate-arn {{ item.value.arn }} \
     {% endif %}
     --certificate file://{{ inventory_dir | dirname }}/work/{{ item.value.files.crt.org_file }} \
     --certificate-chain file://{{ inventory_dir | dirname }}/work/{{ item.value.files.chain.org_file }} \
     --private-key file://{{ inventory_dir | dirname }}/work/{{ item.value.files.key.org_file }} \
     --output text \
     ) && \
    aws acm add-tags-to-certificate \
     --certificate-arn ${CRT_ARN} \
     --tags Key=Name,Value={{ item.value.name }} \
      {% set list = [] %}
      {%- for i in range(item.value.tags|length) -%}
      {%-   set _ = list.append("Key="+item.value.tags[i]['key']+",Value="+item.value.tags[i]['value']) -%}
      {%- endfor -%}
      {{ list | join(' ')}} && \
    echo ${CRT_ARN}
  with_dict: "{{ my_vars.aws.acm.ssl_crt }}"
  when: >-
    not ansible_check_mode
    and year_tag_dict["{{ item.value.name }}"] != "{{ item.value.tags[0].value }}"
  register: acm_crt

- name: save ARN
  template:
    src: save_arn.j2
    dest: "{{ inventory_dir | dirname }}/work/{{ item.item.value.name }}_arn.txt"
    mode: 0644
    group: wheel
  delegate_to: localhost
  changed_when: no
  become: yes
  with_items: "{{ acm_crt.results | default([]) }}"
  when: item.stdout is defined and not ansible_check_mode

template

roles/acm/templates/save_arn.j2
{{ item.stdout }}

site.yml

site.yml
---
- name: acm
  hosts: localhost
  connection: local
  roles:
    - role: acm

実行

Command
$ ansible-playbook -i hosts/aws -l localhost site.yml

まとめ

ACMは、独自取得SSL証明書をELBやCloudFrontにアタッチする際にも利用できます。
特にワイルドカード証明書を複数のELBで利用している場合、SSL証明書の更新作業が1回で済むので便利です。

AnsibleにはACM用モジュールがないため、Ansible内でAWS CLIを強引に実行していますが、ループ処理やテンプレートなどAnsibleの機能が使えるため、シェルスクリプトで全て実装するよりは簡単かと思います。

AnsibleでAWS CLIを実行する場合の参考になれば。

参考

AnsibleでAWSリソースを管理するシリーズ

続きを読む

AWSのLambdaとDynamoDBとAPIGatewayの連携

2017/7/8 現在の情報です。

作る順番

DynamoDB→Lambda→APIGatewayの順番に作っていきます。

下準備~IAMロールの作成~

IAMにロールの作成が必要です。
今回はLambdaからDynamoDBを呼び出させるので先に以下作業を行っておきます。

IAM > ロール > 新しいロールの作成

ロールタイプの選択画面が出るので、「AWS Lambda」を選択
ポリシーの設定画面が出るので

  • AmazonDynamoDBFullAccess
  • AWSLambdaDynamoDBExecutionRole

を選択します。
※他の記事を読むと、「AWSLambdaDynamoDBExecutionRole」だけで良いと書いてあったんですが
 なんかダメでした。

名前決めろって言われるので適当に「lambda-dynamodb-execution-role」とかにします。

で、ためらわず作成。

DynamoDBの設定

DynamoDBの画面よりテーブルの作成ボタンを押す
テーブル名、PKを決めろって言われるので適当に決める。
今回は適当にHogeテーブル、fugaカラム名にします。

しばらくしたら出来上がるので、項目タブ > 項目作成
ここでキー以外の項目を追加できます。piyoを追加しましょう。
追加する時についでに値も入れられるようです。

分かりにくいので図。

dynamo.png

左の方にあるプラスを押すと項目が増えていきます。
型を設定して、値を入れたら保存。するとpiyoカラムが追加されて、ついでに値も入ります。

はい、これでDynamoDBの設定は終わりです。簡単ですね。

Lambdaの設定

はい、次はLambdaです。

Lambdaの画面でためらわず、Lambda関数の作成を押します。

設計図の選択と言われるので、何言ってるか分からないからBlankFunctionを選びます。
トリガーの設定画面が出ますが、何かよく分からんので次へ。
はい、次の画面、大事です。

名前:fugafuga
説明:ぴよぴよ
ランタイム:Node.js 4.3
Lambda 関数のコード
 コードをインラインで編集 を選ぶ。

こんな感じのコードを書きましょう。

var AWS = require('aws-sdk');
var dynamo = new AWS.DynamoDB({
    region: 'ap-northeast-1'
});
exports.handler = function(event, context) {
    var params = {
        "TableName": "Hoge",
        "KeyConditionExpression":"fuga = :fugafuga",
        "ExpressionAttributeValues": {
            ":fugafuga" : {"S": event.fugafuga}
        }
    };
    console.log("event:", event);
    dynamo.query(params, function(err, data) {
        console.log("dynamo_data:", data);
        console.log("dynamo_err:", err);
        context.done(null, data);
    });

};

node.js読めなくても雰囲気でわかると思います。
Dynamoにつないで、Hogeテーブルのfugaテーブルがevent.fugafugaで取得した値と
同じものを取得しそうですね。

その下の方の「Lambda 関数ハンドラおよびロール」の設定。
ハンドラを「exports.handler」と入力して下さい。
そして、ついに出ました。ロールで最初に作った「lambda-dynamodb-execution-role」を選択します。

次へ、関数を作成をためらわずに押します。

Lambdaのテスト

テストしてみましょう。
関数作ったら上の方に嫌でも目につくテストボタン。押してみます。

謎の入力画面。パラメータを指定できそうなのでこんな感じで入力します。

{
  "fugafuga": "ThisisKey"
}

はい、やったね。上手くいきました。

{
  "Items": [
    {
      "fuga": {
        "S": "ThisisKey"
      },
      "piyo": {
        "S": "piyopiyopiyo"
      }
    }
  ],
  "Count": 1,
  "ScannedCount": 1
}

ぴよぴよ言ってますね。

APIGatewayの設定

はい、APIGatewayです。何のためらいもなく、APIの作成ボタンを押しましょう。

新しいAPIです。API名は適当に「piyopiyo」、説明も適当に「ぴよぴよ」と入力しましょう。
するとこんな画面になります。まずはメソッドを作りましょう。
このセレクトを選んで下さい。

apigateway.png

中身を取得したいのでGETメソッドを作りましょう。
すると何か聞かれるので「統合タイプ:Lambda関数」を選びます。せっかく作ったしね。
リージョンはap-northeast-1、Lambda関数はfugafugaでした。

保存ボタンを押すと、何か怖そうなダイアログが出ますが、怖くない、怖くない。
OKしましょう。

するとなんかイケてる画面になります。
この画面が、どこからどうAPIが呼び出されて、その後何を呼び出して、何を返すのか。
禅問答みたいですね。

apigateway2.PNG

パラメータの設定

APIたるものパラメータを受けて渡す必要があるでしょう。
まず受け方は、↑の図の「メソッドリクエスト」をクリックします。
すると色々設定デキそうですが今回は「URL クエリ文字列パラメータ」を設定します。
適当に「fuga」を設定します。これでfugaパラメータを受け取れるようになりました。

次に「統合リクエスト」をクリックします。
ここでは、APIGatewayからLambdaにパラメータを渡す設定をします。

本文マッピングテンプレートが隠れているので展開して
「リクエスト本文のパススルー」を「テンプレートが定義されていない場合 (推奨) 」を選択
その下の「マッピングテンプレートの追加」を押します。
するとContentTypeが設定できるので「application/json」を選択。
しかしひどいUI。

すると、その下に謎の入力箇所が現れるので、ここでやっと引き渡す値を設定できます。

{
    "fugafuga": "$input.params('fuga')"
}

はい、意味分からんですね。
$input.params(‘fuga’) は、APIGatewayが受ける変数です。?fuga=XXX みたいな感じです。
で、左の”fugafuga”は、Lambdaのeventが受け取る変数名になります。
うえーーの方のLambdaのプログラムでevent.fugafuga って書いてたアレに入ります。

最後。「メソッドレスポンス」を選択。
普通に返却すると日本語が化けるらしく、「本文マッピングテンプレート」にこれを設定します。
「application/json;charset=UTF-8」
もともとあった「application/json」は消します。

これで完成。

APIのデプロイから実行

はい、後はAPIをデプロイすれば使えます。
ここからデプロイを選びましょう。

apigateway3.PNG

デプロイされるステージ:新しいステージ
ステージ名:develop
説明:適当に。
ステージは本番、開発を分けるためのタグですね。
実際は何でも大丈夫です。

はい、デプロイするとURLが発行されます。
https://XXXXXX-api.ap-northeast-1.amazonaws.com/develop
みたいな感じになりますね。

これを押すと、nullが表示されると思います。寂しい。
なぜならパラメータが必要だから。
じゃあパラメータfugaを付けてみましょう。
https://XXXXXX-api.ap-northeast-1.amazonaws.com/develop?fuga=ThisisKey

{"Items":[{"fuga":{"S":"ThisisKey"},
"piyo":{"S":"piyopiyopiyo"}}],"Count":1,"ScannedCount":1}

やった!DynamoDBに入れた値が取れましたね。

はい、これで一連の作業はおしまいです。
結構調べながらやると、管理画面が過去の文言だったりフローが変わってたりと
罠が多かったので2017/7/8時点の情報でまとめました。
誰かの役に立つといいなー:sunny:

続きを読む

EC2 Systems ManagerでLinuxのパッチを適用してみた

Linuxの環境に対してPatchを適用できるようになったようです。

Amazon EC2 Systems Manager Now Supports Linux Patching

Previously, Patch Manager only supported Windows managed instances. 
If you wanted to patch Linux managed instances, you needed to use in-house or Linux distribution-specific tools. 
Now, you can manage Linux patches for AWS and on-premises managed instances using the same tool as you do for Windows. 
Just like with Windows patching, Patch Manager lets you automate your Linux patching process using defined auto-approval rules, which lets you only deploy vetted packages. 
This also gives you a single patch compliance view for both your Linux and Windows managed instances.

これまで、Windows Serverに対してはメンテナンスウィンドウやパッチベースラインを利用してパッチの適用ができていましたが、Linuxでも同じ要領でパッチの適用ができるようになったようです。

これは時間の節約ができそうです。早速試します。

Management Consoleを確認

ドキュメント

“AWS-RunPatchBaseline”なるドキュメントが提供されていました。

EC2 Management Console_2.png

パッチベースライン

Amazon Linux、RHEL、Ubuntu用のパッチベースラインが提供されていました。

EC2 Management Console.png

使ってみる

東京リージョンで試します。

コマンド
export AWS_DEFAULT_REGION="ap-northeast-1"

メンテナンスウィンドウの作成

まず、メンテナンスウィンドウを作成します。(これまでと同じです)

コマンド
aws ssm create-maintenance-window 
    --name "LinuxPatching" 
    --no-allow-unassociated-targets 
    --schedule "cron(0 10 10 ? * SAT *)" 
    --duration 2 
    --cutoff 1
結果
{
    "WindowId": "mw-xxxxxxxxxxxxxxxxx"
}

ターゲットとなるLinuxインスタンスの作成

今回はAmazon Linuxを用意します。

ログインするのがめんどいので、UserDataでSSM Agentをインストールします。

また、インターネットにアクセス可能なSubnetに配置しましょう。また、適切なインスタンスプロファイルを設定します。

EC2 Systems Managerの動作要件は、以下のドキュメントを確認してください。

UserData
#!/bin/bash
cd /tmp
sudo yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm

ターゲットの指定

ここもこれまでの手順と変わりません。

コマンド
MAINTENANCE_WINDOW_ID="mw-xxxxxxxxxxxxxxxxx"
INSTANCE_ID="i-xxxxxxxxxxxxxxxxx"
コマンド
aws ssm register-target-with-maintenance-window 
    --window-id ${MAINTENANCE_WINDOW_ID} 
    --resource-type "INSTANCE" 
    --targets "Key=InstanceIds,Values=${INSTANCE_ID}"
結果
{
    "WindowTargetId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

(割愛)パッチベースラインの作成

今回はデフォルトのパッチベースラインと使って動作を確認します。

パッチベースラインの一覧を確認します。

コマンド
aws ssm describe-patch-baselines
結果
{
    "BaselineIdentities": [
        {
            "BaselineName": "AWS-AmazonLinuxDefaultPatchBaseline",
            "DefaultBaseline": true,
            "BaselineDescription": "Default Patch Baseline for Amazon Linux Prov                                                                                                                                                            ided by AWS.",
            "BaselineId": "arn:aws:ssm:ap-northeast-1:486716784251:patchbaseline                                                                                                                                                            /pb-0221829c157d721d8",
            "OperatingSystem": "AMAZON_LINUX"
        },
        {
            "BaselineName": "AWS-DefaultPatchBaseline",
            "DefaultBaseline": true,
            "BaselineDescription": "Default Patch Baseline Provided by AWS.",
            "BaselineId": "arn:aws:ssm:ap-northeast-1:486716784251:patchbaseline                                                                                                                                                            /pb-04ba050f612fba3a6",
            "OperatingSystem": "WINDOWS"
        },
        {
            "BaselineName": "AWS-RedHatDefaultPatchBaseline",
            "DefaultBaseline": true,
            "BaselineDescription": "Default Patch Baseline for Redhat Enterprise                                                                                                                                                             Linux Provided by AWS.",
            "BaselineId": "arn:aws:ssm:ap-northeast-1:486716784251:patchbaseline                                                                                                                                                            /pb-0adf5cb7136a2984d",
            "OperatingSystem": "REDHAT_ENTERPRISE_LINUX"
        },
        {
            "BaselineName": "AWS-UbuntuDefaultPatchBaseline",
            "DefaultBaseline": true,
            "BaselineDescription": "Default Patch Baseline for Ubuntu Provided b                                                                                                                                                            y AWS.",
            "BaselineId": "arn:aws:ssm:ap-northeast-1:486716784251:patchbaseline                                                                                                                                                            /pb-0ec96a11368349171",
            "OperatingSystem": "UBUNTU"
        }
    ]
}

Amazon Linux用のパッチベースラインを確認します。

コマンド
aws ssm get-patch-baseline 
    --baseline-id arn:aws:ssm:ap-northeast-1:486716784251:patchbaseline/pb-0221829c157d721d8
結果
{
    "BaselineId": "arn:aws:ssm:ap-northeast-1:486716784251:patchbaseline/pb-0221829c157d721d8",
    "Name": "AWS-AmazonLinuxDefaultPatchBaseline",
    "PatchGroups": [],
    "RejectedPatches": [],
    "GlobalFilters": {
        "PatchFilters": [
            {
                "Values": [
                    "AmazonLinux2012.03",
                    "AmazonLinux2012.09",
                    "AmazonLinux2013.03",
                    "AmazonLinux2013.09",
                    "AmazonLinux2014.03",
                    "AmazonLinux2014.09",
                    "AmazonLinux2015.03",
                    "AmazonLinux2015.09",
                    "AmazonLinux2016.03",
                    "AmazonLinux2016.09",
                    "AmazonLinux2017.03",
                    "AmazonLinux2017.09"
                ],
                "Key": "PRODUCT"
            }
        ]
    },
    "ApprovalRules": {
        "PatchRules": [
            {
                "PatchFilterGroup": {
                    "PatchFilters": [
                        {
                            "Values": [
                                "Security"
                            ],
                            "Key": "CLASSIFICATION"
                        },
                        {
                            "Values": [
                                "Critical",
                                "Important"
                            ],
                            "Key": "SEVERITY"
                        }
                    ]
                },
                "ApproveAfterDays": 7,
                "ComplianceLevel": "UNSPECIFIED"
            },
            {
                "PatchFilterGroup": {
                    "PatchFilters": [
                        {
                            "Values": [
                                "Bugfix"
                            ],
                            "Key": "CLASSIFICATION"
                        }
                    ]
                },
                "ApproveAfterDays": 7,
                "ComplianceLevel": "UNSPECIFIED"
            }
        ]
    },
    "ModifiedDate": 1499203527.709,
    "CreatedDate": 1499203527.709,
    "ApprovedPatchesComplianceLevel": "UNSPECIFIED",
    "OperatingSystem": "AMAZON_LINUX",
    "ApprovedPatches": [],
    "Description": "Default Patch Baseline for Amazon Linux Provided by AWS."
}

タスクを指定

使用するドキュメントを確認します。

コマンド
DOCUMENT_NAME="AWS-RunPatchBaseline"

aws ssm describe-document 
    --name ${DOCUMENT_NAME}
結果
{
    "Document": {
        "Status": "Active",
        "Hash": "d5c29590f323c144ce0338b8424970e2284f780b404dc164b2bc96dae5415218",
        "Name": "AWS-RunPatchBaseline",
        "Parameters": [
            {
                "Type": "String",
                "Name": "Operation",
                "Description": "(Required) The update or configuration to perform on the instance. The system checks if patches specified in the patch baseline are installed on the instance. The install operation installs patches missing from the baseline."
            },
            {
                "DefaultValue": "",
                "Type": "String",
                "Name": "SnapshotId",
                "Description": "(Optional) The snapshot ID to use to retrieve a patch baseline snapshot."
            }
        ],
        "DocumentType": "Command",
        "PlatformTypes": [
            "Linux"
        ],
        "DocumentVersion": "1",
        "HashType": "Sha256",
        "CreatedDate": 1499451553.857,
        "Owner": "Amazon",
        "SchemaVersion": "2.2",
        "DefaultVersion": "1",
        "LatestVersion": "1",
        "Description": "Scans for or installs patches from a patch baseline to a Linux or Windows operating system."
    }
}

タスクを設定する際には、IAMロールを指定する必要があります。

Configuring Roles and Permissions for Maintenance Windows

コマンド
ROLE_ARN_FOR_MAINTENANCE_WINDOW="arn:aws:iam::XXXXXXXXXXXX:role/AmazonSSMMaintenanceWindowRole"

また、実行結果を格納するS3バケットも予め用意しておきます。

コマンド
BUCKET_NAME="XXXXXXXX"
PREFIX="logs"

PATCH_BASELINE_LOGGING_FILE_NAME="patch_baseline_logging.json"

cat << EOF > ${PATCH_BASELINE_LOGGING_FILE_NAME}
{
    "S3BucketName": "${BUCKET_NAME}",
    "S3KeyPrefix": "${PREFIX}",
    "S3Region": "${AWS_DEFAULT_REGION}"
}
EOF

その他のパラメーターを確認し、タスクを登録します。

コマンド
PATCH_BASELINE_PARAMETER_FILE_NAME="patch_baseline_parameter.json"

cat << EOF > ${PATCH_BASELINE_PARAMETER_FILE_NAME}
{
    "Operation": {
        "Values": [
            "Install"
        ]
    }
}
EOF
コマンド
TASK_TYPE="RUN_COMMAND"
PRIORITY="1"
MAX_COCCURRENCY="1"
MAX_ERRORS="1"
コマンド
aws ssm register-task-with-maintenance-window 
    --window-id ${MAINTENANCE_WINDOW_ID} 
    --targets "Key=InstanceIds,Values=${INSTANCE_ID}" 
    --task-arn ${DOCUMENT_NAME} 
    --service-role-arn ${ROLE_ARN_FOR_MAINTENANCE_WINDOW} 
    --task-type ${TASK_TYPE} 
    --task-parameters file://${PATCH_BASELINE_PARAMETER_FILE_NAME} 
    --priority ${PRIORITY} 
    --max-concurrency ${MAX_COCCURRENCY} 
    --max-errors ${MAX_ERRORS} 
    --logging-info file://${PATCH_BASELINE_LOGGING_FILE_NAME}
結果
{
    "WindowTaskId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

実行結果の確認

メンテナンスウィンドウで指定した時刻になるまで待ちます。

細かいところをとばして、S3に出力された実行結果を確認します。

結果
/usr/bin/python
Loaded plugins: priorities, update-motd, upgrade-helper
Resolving Dependencies
--> Running transaction check
---> Package python26-requests.noarch 0:1.2.3-5.10.amzn1 will be installed
--> Processing Dependency: python(abi) = 2.6 for package: python26-requests-1.2.3-5.10.amzn1.noarch
--> Processing Dependency: python26-urllib3 >= 1.7 for package: python26-requests-1.2.3-5.10.amzn1.noarch
--> Processing Dependency: python26(dist-packages) for package: python26-requests-1.2.3-5.10.amzn1.noarch
--> Processing Dependency: python26-chardet for package: python26-requests-1.2.3-5.10.amzn1.noarch
--> Processing Dependency: python26-ordereddict for package: python26-requests-1.2.3-5.10.amzn1.noarch
--> Running transaction check
---> Package python26.x86_64 0:2.6.9-2.88.amzn1 will be installed
--> Processing Dependency: libpython2.6.so.1.0()(64bit) for package: python26-2.6.9-2.88.amzn1.x86_64
---> Package python26-chardet.noarch 0:2.0.1-7.7.amzn1 will be installed
---> Package python26-urllib3.noarch 0:1.8.2-1.5.amzn1 will be installed
--> Processing Dependency: python26-backports-ssl_match_hostname for package: python26-urllib3-1.8.2-1.5.amzn1.noarch
--> Processing Dependency: python26-six for package: python26-urllib3-1.8.2-1.5.amzn1.noarch
--> Running transaction check
---> Package python26-backports-ssl_match_hostname.noarch 0:3.4.0.2-1.12.amzn1 will be installed
--> Processing Dependency: python26-backports for package: python26-backports-ssl_match_hostname-3.4.0.2-1.12.amzn1.noarch
---> Package python26-libs.x86_64 0:2.6.9-2.88.amzn1 will be installed
---> Package python26-six.noarch 0:1.8.0-1.23.amzn1 will be installed
--> Running transaction check
---> Package python26-backports.x86_64 0:1.0-3.14.amzn1 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
 Package                              Arch   Version            Repository
                                                                           Size
================================================================================
Installing:
 python26-requests                    noarch 1.2.3-5.10.amzn1   amzn-main  94 k
Installing for dependencies:
 python26                             x86_64 2.6.9-2.88.amzn1   amzn-main 5.8 M
 python26-backports                   x86_64 1.0-3.14.amzn1     amzn-main 5.2 k
 python26-backports-ssl_match_hostname
                                      noarch 3.4.0.2-1.12.amzn1 amzn-main  12 k
 python26-chardet                     noarch 2.0.1-7.7.amzn1    amzn-main 377 k
 python26-libs                        x86_64 2.6.9-2.88.amzn1   amzn-main 697 k
 python26-six                         noarch 1.8.0-1.23.amzn1   amzn-main  31 k
 python26-urllib3                     noarch 1.8.2-1.5.amzn1    amzn-main  98 k

Transaction Summary
================================================================================
Install  1 Package (+7 Dependent packages)

Total download size: 7.0 M
Installed size: 22 M
Downloading packages:
--------------------------------------------------------------------------------
Total                                              1.9 MB/s | 7.0 MB  00:03     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : python26-2.6.9-2.88.amzn1.x86_64                             1/8 
  Installing : python26-libs-2.6.9-2.88.amzn1.x86_64                        2/8 
  Installing : python26-chardet-2.0.1-7.7.amzn1.noarch                      3/8 
  Installing : python26-six-1.8.0-1.23.amzn1.noarch                         4/8 
  Installing : python26-backports-1.0-3.14.amzn1.x86_64                     5/8 
  Installing : python26-backports-ssl_match_hostname-3.4.0.2-1.12.amzn1.n   6/8 
  Installing : python26-urllib3-1.8.2-1.5.amzn1.noarch                      7/8 
  Installing : python26-requests-1.2.3-5.10.amzn1.noarch                    8/8 
  Verifying  : python26-requests-1.2.3-5.10.amzn1.noarch                    1/8 
  Verifying  : python26-chardet-2.0.1-7.7.amzn1.noarch                      2/8 
  Verifying  : python26-urllib3-1.8.2-1.5.amzn1.noarch                      3/8 
  Verifying  : python26-libs-2.6.9-2.88.amzn1.x86_64                        4/8 
  Verifying  : python26-six-1.8.0-1.23.amzn1.noarch                         5/8 
  Verifying  : python26-2.6.9-2.88.amzn1.x86_64                             6/8 
  Verifying  : python26-backports-1.0-3.14.amzn1.x86_64                     7/8 
  Verifying  : python26-backports-ssl_match_hostname-3.4.0.2-1.12.amzn1.n   8/8 

Installed:
  python26-requests.noarch 0:1.2.3-5.10.amzn1                                   

Dependency Installed:
  python26.x86_64 0:2.6.9-2.88.amzn1                                            
  python26-backports.x86_64 0:1.0-3.14.amzn1                                    
  python26-backports-ssl_match_hostname.noarch 0:3.4.0.2-1.12.amzn1             
  python26-chardet.noarch 0:2.0.1-7.7.amzn1                                     
  python26-libs.x86_64 0:2.6.9-2.88.amzn1                                       
  python26-six.noarch 0:1.8.0-1.23.amzn1                                        
  python26-urllib3.noarch 0:1.8.2-1.5.amzn1                                     

Complete!
07/08/2017 10:30:55 root [INFO]: Attempting to acquire instance information from ssm-cli.
07/08/2017 10:30:55 root [INFO]: ssm-cli path is: /usr/bin/ssm-cli.
07/08/2017 10:30:55 root [INFO]: Instance metadata from ssm-cli is: {"instance-id":"i-xxxxxxxxxxxxxxxxx","region":"ap-northeast-1","release-version":"2.0.847.0"}.
07/08/2017 10:30:55 urllib3.connectionpool [INFO]: Starting new HTTPS connection (1): s3.dualstack.ap-northeast-1.amazonaws.com
07/08/2017 10:30:56 urllib3.connectionpool [INFO]: Starting new HTTPS connection (1): s3.dualstack.ap-northeast-1.amazonaws.com
07/08/2017 10:30:57 root [INFO]: Attempting to acquire instance information from ssm-cli.
07/08/2017 10:30:57 root [INFO]: ssm-cli path is: /usr/bin/ssm-cli.
07/08/2017 10:30:57 root [INFO]: Instance metadata from ssm-cli is: {"instance-id":"i-xxxxxxxxxxxxxxxxx","region":"ap-northeast-1","release-version":"2.0.847.0"}.
07/08/2017 10:30:57 root [INFO]: Operation type: Install.
07/08/2017 10:30:57 root [INFO]: Snapshot ID: e62016f7-257d-4aee-b520-b58b10ad3cdd.
07/08/2017 10:30:57 root [INFO]: Instance ID: i-xxxxxxxxxxxxxxxxx.
07/08/2017 10:30:57 root [INFO]: Region ID: ap-northeast-1.
07/08/2017 10:30:57 botocore.vendored.requests.packages.urllib3.connectionpool [INFO]: Starting new HTTP connection (1): 169.254.169.254
07/08/2017 10:30:57 botocore.vendored.requests.packages.urllib3.connectionpool [INFO]: Starting new HTTP connection (1): 169.254.169.254
07/08/2017 10:30:57 botocore.vendored.requests.packages.urllib3.connectionpool [INFO]: Starting new HTTPS connection (1): ssm.ap-northeast-1.amazonaws.com
07/08/2017 10:30:57 root [INFO]: Product: AmazonLinux2017.03.
07/08/2017 10:30:57 root [INFO]: Snapshot download URL: https://patch-baseline-snapshot-ap-northeast-1.s3-ap-northeast-1.amazonaws.com/325e6910d69bd8861bea653821b277cecf2df85952414e32ec904b9a32a4a88b-788063364413/AMAZON_LINUX-e62016f7-257d-4aee-b520-b58b10ad3cdd?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20170708T103057Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86400&X-Amz-Credential=AKIAILTSCHNHVBZOKOSA%2F20170708%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Signature=833c5f22664d48aa9b6fe5a4db9d04ee878293efb56d3bd581e1e8f0ce421de7.
07/08/2017 10:30:57 urllib3.connectionpool [INFO]: Starting new HTTPS connection (1): patch-baseline-snapshot-ap-northeast-1.s3-ap-northeast-1.amazonaws.com
07/08/2017 10:30:57 root [INFO]: Patch baseline: {u'baselineId': u'pb-0221829c157d721d8', u'name': u'AWS-AmazonLinuxDefaultPatchBaseline', u'modifiedTime': 1499203527.709, u'description': u'Default Patch Baseline for Amazon Linux Provided by AWS.', u'rejectedPatches': [], u'globalFilters': {u'filters': [{u'values': [u'AmazonLinux2012.03', u'AmazonLinux2012.09', u'AmazonLinux2013.03', u'AmazonLinux2013.09', u'AmazonLinux2014.03', u'AmazonLinux2014.09', u'AmazonLinux2015.03', u'AmazonLinux2015.09', u'AmazonLinux2016.03', u'AmazonLinux2016.09', u'AmazonLinux2017.03', u'AmazonLinux2017.09'], u'key': u'PRODUCT'}]}, u'approvalRules': {u'rules': [{u'filterGroup': {u'filters': [{u'values': [u'Security'], u'key': u'CLASSIFICATION'}, {u'values': [u'Critical', u'Important'], u'key': u'SEVERITY'}]}, u'complianceLevel': u'UNSPECIFIED', u'approveAfterDays': 7}, {u'filterGroup': {u'filters': [{u'values': [u'Bugfix'], u'key': u'CLASSIFICATION'}]}, u'complianceLevel': u'UNSPECIFIED', u'approveAfterDays': 7}]}, u'createdTime': 1499203527.709, u'approvedPatchesComplianceLevel': u'UNSPECIFIED', u'operatingSystem': u'AMAZON_LINUX', u'approvedPatches': [], u'accountId': u'486716784251'}.
07/08/2017 10:30:57 root [INFO]: Patch group: .
07/08/2017 10:30:57 root [INFO]: Operating system: AMAZON_LINUX.
07/08/2017 10:30:57 root [WARNING]: Unable to gain necessary access for possible kernel updates, code: 1.
07/08/2017 10:30:58 botocore.vendored.requests.packages.urllib3.connectionpool [INFO]: Starting new HTTP connection (1): 169.254.169.254
07/08/2017 10:30:58 botocore.vendored.requests.packages.urllib3.connectionpool [INFO]: Starting new HTTP connection (1): 169.254.169.254
Loaded plugins: priorities, update-motd, upgrade-helper
07/08/2017 10:30:58 root [INFO]: No updates, skipping.
Loaded plugins: priorities, update-motd, upgrade-helper
07/08/2017 10:30:59 root [INFO]: 
Patch compliance initialized with instance ID:i-xxxxxxxxxxxxxxxxx, 
baseline ID: pb-0221829c157d721d8, snapshot ID: e62016f7-257d-4aee-b520-b58b10ad3cdd, patch group: ,
start time: 2017-07-08 10:30:58.177869, end time: 2017-07-08 10:30:59.590999, upload NA compliance: False

07/08/2017 10:30:59 root [INFO]: Start to upload patch compliance.
07/08/2017 10:30:59 root [INFO]: Summary: {'ContentHash': 'b71836e461121012e37c8f25eb2846ade95a267c9fc237d5e5af36deaf8048f8', 'TypeName': 'AWS:PatchSummary', 'SchemaVersion': '1.0', 'CaptureTime': '2017-07-08T10:30:59Z', 'Content': [{'OperationStartTime': '2017-07-08T10:30:58Z', 'FailedCount': '0', 'PatchGroup': u'', 'OperationType': u'Install', 'BaselineId': u'pb-0221829c157d721d8', 'MissingCount': '0', 'NotApplicableCount': '249', 'OperationEndTime': '2017-07-08T10:30:59Z', 'InstalledOtherCount': '377', 'SnapshotId': u'e62016f7-257d-4aee-b520-b58b10ad3cdd', 'InstalledCount': '20'}]}
07/08/2017 10:30:59 botocore.vendored.requests.packages.urllib3.connectionpool [INFO]: Starting new HTTPS connection (1): ssm.ap-northeast-1.amazonaws.com
07/08/2017 10:31:00 root [INFO]: Upload complete.
07/08/2017 10:31:00 root [INFO]: Report upload is successful.

感想

動作確認をどうするのかとか、適用を除外したい/別途ラインでは該当しないけど適用したいなどの例外処理(パッチベースラインで設定可能)をどうするのかなど、予め考えていくことはありますが、できるだけ仕事しなくていいようにがんばりましょう!

続きを読む