Golangの&や*

The Go Programming Language Specification - The Go Programming Language

変数の宣言

var hoge = "hoge"

&

ポインター

log.Println(&hoge)
↓
0xc82000ade0

*

ポインターの値

log.Println(*&hoge)
↓
hoge

log.Println(*hoge)
↓
invalid indirect of hoge (type string)
Remove all ads

Golangの変数の使い分け

The Go Programming Language Specification - The Go Programming Language

var

変数の宣言用

:=

関数の返り値格納用

すべて:=に統一でも良いかもしれません。

Golangのflag

Golangコマンドラインツールやバッチを作る際によく使うのでメモ

flag - The Go Programming Language

  • flagの渡し方
go run main.go -user google  -app golang
  • flagの設定
var (
  user = flag.String("user", "google", "user name")
  app  = flag.String("app", "golang", "app Name")
)
  • 渡されたflagを設定に紐付ける
flag.Parse()
  • 渡されたflagの数を取得
nflag := flag.NFlag()
  • 渡された値の取得
*user
*app
Remove all ads

ACM(AWSCertificateManager)の発行承認メール(domain validation email)がドメイン登録時のアドレスに来ない場合

Route53のRegistered DomainsのPrivacy Protectionを以下のように変更する。

Hide contact information if the TLD registry, and the registrar, allow it
↓
Don't hide contact information

本来は@ドメイン名でメールを受け取れるようにすべきようです。

Validate Domain Ownership - AWS Certificate Manager

AutoScaling(オートスケーリング)時のデプロイのベストプラクティス(CodeDeploy)

AutoScaling(オートスケーリング)時にはLaunchConfigのAMIを元に新しいインスタンスが作成されます。
しかし、GitHub等のリビジョンが進んでいると新しいインスタンスのみ古いリビジョンのソースになってしまいます。
そのため、主に以下の2つの方法があります。

デプロイの度にAMIを作成して、AutoScalingGroup(オートスケーリンググループ)とELBを差し替える

サーバー内の変更時もデプロイ時も常に同じことをすればいいので、判断が不要になることが利点です。
しかし、デプロイの度にAMIを作成して、AutoScalingGroupとELBを作成して、
CodeDeployのApplicationStopイベントが初回のデプロイ時には動かないため、
AutoScalingGroup(オートスケーリンググループ)に対して1回デプロイして、
Route53のCNAMEレコードを切り替えて、旧リソースを削除してと手順が多いです。

また、GitHubでプルリクがマージされた際に上記手順が実行されるように自動化するのは手間がかかります。
結果的に手動になり、事故の元となります。

DeploymentGroup(CodeDeploy)の対象をAutoScalingにする

新しいインスタンスが追加される前に自動的にGitHubの最新のリビジョンがデプロイされます。
デプロイの際にはデプロイの実行のみでよいです。
GitHubのプルリクがマージされた際に自動的にデプロイされるように簡単に設定することができます。

上記から基本的にAutoScaling(オートスケーリング)のデプロイにはCodeDeployを使用すべきです。
そこで詳細を記載致します。

AutoScaling(オートスケーリング)に対するDeploymentGroup(CodeDeploy)を設定した際に何が設定されているか

  • CodeDeploy用のLifecycleHookが設定される
$ aws autoscaling describe-lifecycle-hooks --auto-scaling-group-name XXXXXX
        {
            "AutoScalingGroupName": "XXXX-AutoScalingStack-XXXX-AutoScalingGroup-XXXX",
            "HeartbeatTimeout": 600,
            "DefaultResult": "ABANDON",
            "NotificationMetadata": "XXXXXXXXXXXX",
            "NotificationTargetARN": "XXXXXXXXXXX",
            "LifecycleHookName": "CodeDeploy-managed-automatic-launch-deployment-hook-production-XXXX",
            "GlobalTimeout": 60000,
            "LifecycleTransition": "autoscaling:EC2_INSTANCE_LAUNCHING"
        }

全体像は以下の図です。

AutoScaling時の挙動

f:id:keiwt:20160910203602p:plain

上記図を元に考慮すべきベストプラクティスは以下の通りです。

ベスト・プラクティス

  • CustomAMIではcodedeploy-agentを停止して、起動時にstartする
    ※CodeDeployを動かせたいタイミングでstartします
        "UserData": { "Fn::Base64" : { "Fn::Join" : ["", [
          "#!/bin/bash -xe\n",
          "# Install the files and packages from the metadata\n",
          "/opt/aws/bin/cfn-init",
          " --stack ", { "Ref" : "AWS::StackName" },
          " --resource LaunchConfig",
          " --configsets Install",
          " --region ", { "Ref" : "AWS::Region" }, "\n",

          "service codedeploy-agent start\n",

          "# Signal the status from cfn-init\n",
          "/opt/aws/bin/cfn-signal -e $?",
          " --stack ", { "Ref" : "AWS::StackName" },
          " --resource AutoScalingGroup",
          " --region ", { "Ref" : "AWS::Region" }, "\n"
        ]]}}
通常の場合は1リポジトリになっていると思います。  
もし、1AutoScalingGroup複数リポジトリになっている場合はアーキテクチャがおかしいです。 
AutoScalingGroupを分けるべきです。
※フロント、管理画面、API、バッチサーバー等が同居しているようなおかしいケースが該当します。
※1サーバー1機能

Troubleshoot Auto Scaling Issues - AWS CodeDeploy

参考・引用元

Under the Hood: AWS CodeDeploy and Auto Scaling Integration - AWS DevOps Blog - Amazon Web Services
Integrating AWS CodeDeploy with Auto Scaling - AWS CodeDeploy

HTTPS化のベスト・プラクティス(AWSのRoute53,ACM,ELB,CloudFrontを使用する)

実質HTTPSデファクトになっていますので、HTTPS化する方法をご紹介致します。
※HTTP2はHTTPSを前提としています
Appleはアプリで使用するAPIHTTPSを強制化します

AWSさんのおかげで、HTTPS化するのがとても簡単になっていますので、もはやHTTPを使う理由はないと思います。

ドメインの取得

Route53

ACMで証明書を発行する際にはここで登録したメールアドレスに承認メールがきます

ACMで証明書の取得

    "AcmCertificate" : {
      "Type" : "AWS::CertificateManager::Certificate",
      "Properties" : {
        "DomainName" : { "Ref" :"SiteDomain" },
        "DomainValidationOptions" : [{
          "DomainName" : { "Ref" : "SiteDomain" },
          "ValidationDomain" : { "Ref" : "SiteDomain" }
        }],
        "Tags": [
          { "Key": "Name", "Value": { "Ref": "AWS::StackName" } }
        ]
      }
    }

ドメイン名でメールアドレスを受け取れるようにする必要はありません
※Route53でドメイン取得時に登録したメールアドレスに承認メールがきます
※承認メールのI approveボタンをクリックするだけで証明書が発行済みになります

ELB(クラシック)の場合

SSLCertificateIdに証明書のarnを指定するだけです

        "Listeners": [
          {
            "InstancePort"    : "80",
            "InstanceProtocol": "HTTP",
            "LoadBalancerPort": "443",
            "Protocol"        : "HTTPS",
            "SSLCertificateId": { "Ref" : "AcmCertificateArn" }
          }
        ]

ALB(ELBV2)の場合

CertificateArnに証明書のarnを指定するだけです

    "Listener": {
      "Type": "AWS::ElasticLoadBalancingV2::Listener",
      "Properties": {
        "Certificates" : [{
          "CertificateArn" : { "Ref" : "AcmCertificateArn" }
        }],
        "DefaultActions": [
          {
            "Type": "forward",
            "TargetGroupArn": { "Ref": "TargetGroup" }
          }
        ],
        "LoadBalancerArn": { "Ref": "ELBV2" },
        "Port": "443",
        "Protocol": "HTTPS"
      }
    }

CloudFrontの場合

httpsのみ許可して、AcmCertificateArnに証明書のarnを指定するだけです

    "Distribution" : {
      "Type" : "AWS::CloudFront::Distribution",
      "Properties" : {
        "DistributionConfig" : {
          "DefaultCacheBehavior" : {
            ...
            "ViewerProtocolPolicy" : "https-only"
          },
          ...
          "ViewerCertificate": {
            "AcmCertificateArn": { "Ref" : "AcmCertificateArn" },
            "MinimumProtocolVersion": "TLSv1",
            "SslSupportMethod": "sni-only"
          },
          ...
        }
      }

Web API: The Good Partsを読んでみて、ApiGatewayのベスト・プラクティスについて考えてみる

ApiGatewayでAPIを設定するに当たっての要点をまとめてみます。 例は本の内容を参考に独自のものにしています。

https://www.amazon.co.jp/Web-API-Parts-%E6%B0%B4%E9%87%8E-%E8%B2%B4%E6%98%8E/dp/4873116864

URL

https://api.example.com/XXXXXXXX/XXXXXXXXXXXX/
  • すべて小文字にする
https://developer.github.com/v3/git/commits/
https://api.twitter.com/1.1/statuses/retweets/:id.json
↓
AWS::ApiGateway::ResourceのPathPartは全て小文字にする
  • 複数形の名詞を使用する
users
activities
※get等の動詞は含めない
↓
DynamoDBのテーブル名、AWS::ApiGateway::ResourceのLogicalIDを複数形の名詞
  • 複数単語はパスを分ける
サービス URL 修正後URL
Twitter /statuses/user_timeline /statuses/user/timeline/
Youtube /guideCategories guide/categories
Facebook /me/books.quotes me/books/quotes/
LinkedIn /v1/people-search vi/people/search
Bit.ly /v3/user/popular_earned_by_clicks v3/user/popular/clicks
Disqus /api/3.0/applications/listUsage.json /api/3.0/applications/list/usage.json
  • 取得数と取得位置
limitとoffsetにする
※SQLでもlimitとoffsetが使用されている
※取得数はcount,per_page,maxResultsを使っているのもあるが少数
※取得位置はstart,page,cursorを使っているのもあるが少数
q or query
※googleはqを使用している
  • パス or クエリ
一意なリソースを表すのに必要な情報の場合はパス
※id等

省略可能でデフォルトが存在するものはクエリ
※offsetやlimit
v1 or 1.0

api.twitter.com/1.1
api.foursquare.com/v2
www.googleapis.com/youtube/v3
api.linkedin.com/v1
developer.github.com/v3

※以下は通常のAPIとはURLが異なるようです
app.rakuten.co.jp/services/api
webservice.recruit.co.jp/hotpepper
webservice.recruit.co.jp/r25
jws.jalan.net/APIAdvance
api.mixi-platform.com/2
  • メソッド
X-HTTP-Method-Overrideヘッダーを使用する
メソッド名 操作
GET リソースの取得
POST リソースの新規登録
PUT 既存リソースの更新
DELETE リソースの削除
PATCH リソースの一部変更
HEAD リソースのメタ情報の取得
  • 個々のデータ取得
id or id.json
  • 一覧
XXX/list or XXX/list.json or XXX/XXXies
  • 更新
XXX/updates or XXX/id

レスポンス

  • 取得するフィールドを選べるようにする
https://api.exmample.com/v1/users/12345?fields=name,age
or
https://api.exmample.com/v1/users/12345?ResponseGroup=Medium

Response Groups - Product Advertising API

  • エンベロープ

    HTTP プロトコルというエンベロープがあるので、2重に封筒に入れる意味はない

NG

{
  "header": {
    "status": "success",
    "errorCode": 0,
  },
  "response": {
    ... 実際のデータ ...
  }
}
配列にしない
※JSONインジェクションを防ぐ
  • 件数や次の結果の存在の有無
{
  "timelines":[
    :
  ],
  hasNext: true
}
{
  "kind": "plus#activityFeed",
  "title": "Plus Public Activities Feed",
  "nextPageToken": "CKaEL",
  "items": [
    {
      "kind": "plus#activity",
      "id": "123456789",
      ...
    },
  ]
}

なるべく少ない単語数で表現する

NG:userRegistrationDateTime
※/users というユーザー情報を取得する API なら、最初の user はな くても問題ない

複数の単語を連結する場合、その連結方法は API 全体を通して統一する

キャメル
※jsonなので

変な省略形は極力利用しない

NG: timezone→tz
NG: location→lctn
  • 性別
gender: male|female
※maleを1等にはしない
  • 数値 誤差が出ないように文字列として扱う
462781738297483264がと462781738297483260される。
これはJavaScriptが大きな整数を扱うと誤差が出てしまうから

"id": 266031293949698048,
"id_str": "266031293949698048"
  • エラー

    クライアントからのリクエストが成功した場合しか200 番台のリクエストは返してはならない、という点です。まれにパラメータが間違っていたり、権限がなかったりしてエラーになった場合に、データとしてはエラー情報が返ってくるものの、ステータスコードは 200 を返しているケースがありますが、これは使い方として正しくなく、 実際に問題も引き起こします。

  • エラーの詳細をクライアントに返す
    twitter

{
  "errors":[
    {
      "message":"Bad Authentication data",
      "code":215
    }
  ]
}

GitHub

{
  "message": "Not Found",
  "documentation_url": "https://developer.github.com/v3"
}
200を返して、レスポンスボディで独自ステータスコードを返す
  • 202(Accepted)の実例
承認を必要とする場合は202
ファイルのダウンロードをする際はRetry-After ヘッダとともに202
  • PUTやPATCH
200と共に生成したデータを返す
※賛否両論あり
  • DELETE
204(No Content)を返す
※賛否両論あり
  • Expires or Cache-Control
更新日時が決まっているものはExpires
他はCache-Controlを使用する
  • Dateヘッダを必ず返す(500番台以外)
Date: Wed, 20 Aug 2014 11:10:39 GMT
※必ずHTTP時間の形式でGMTで(日本でも)
  • Last-ModifiedとETagを使って、クライアントはキャッシュがfreshか確認
Last-Modified: Tue, 01 Jul 2014 00:00:00 GMT
ETag: "ff39b31e285573ee373af0d492aca581"
  • クライアントにキャッシュしてほしくない場合
Cache-Control: no-cache
※Expiresは使わない
  • クライアントにキャッシュしてほしい場合
Cache-Control: public, max-age=3600
  • 機密情報の場合でプロキシサーバー等にキャッシュしてほしくない場合
Cache-Control: no-store
  • 同じURLでも、リクエストヘッダによって結果が異なるものはVaryヘッダーを指定する
Vary: Accept-Encoding,User-Agent,Accept-Language

Vary: Accept
※Acceptヘッダーに任せる
  • 新鮮ではないキャッシュを持っているキャッシュサーバー
stale-while-revalidateによる非同期のキャッシュの更新を使う
  • Content-Typeは正しく設定する
Content-Type: application/json; charset=utf-8
※iOSのネットワーククライアント等では、Content-Typeが不正な場合はエラーになる
※jsonをtext/htmlにするとXSSが可能になる
  • 独自ヘッダーの例

    HTTP ヘッダを新しく定義する場合はこのように“X-”という接頭辞を最初に付けて、次に サービスやアプリケーション、組織などの名前を付けるというのが一般的です

X-GitHub-Media-Type: github.v3
  • CORS
リクエストヘッダーのOriginを検証して、同じものを以下のようにして返す
Access-Control-Allow-Origin: http://www.example.com
  • ブラウザで自動的に行われるプリフライトリクエスト
OPTIONS /v1/users/12345 HTTP/1.1
Host: api.example.com
Accept: application/json
Origin: http://www.example.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: X-RequestId


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Access-Control-Allow-Origin: http://www.example.com
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: X-RequestId
Access-Control-Max-Age: 864000
Content-Length: 0
Content-Type: text/plain
  • CORSでのユーザー認証情報
Access-Control-Allow-Credentials: true
※認証情報を認識しておりますということ
※なかった場合、ブラウザは受け取ったレスポンスを拒否してしまいます

セキュリティ

man-in-the-middle attack  
クライアントが証明書の警告を無視する場合は可能

www.youtube.com

  • Content-Type ユーザーからの入力を受け入れてJSONに埋め込んで返すようなAPIでContent-Typeがtext/htmlだとXSSが可能になる。 ※X-Content-Type-Options: nosniffもしておかないとブラウザによってはXSSが可能になる。
{"data":"<script>alert('xss');</script>"}
  • エスケープ
/(スラッシュ)
<: \u003C
>:  \u003E 
\: \u005C
\ や "、'などに関しても、\u005Cや\u0022、\u0027のように16進数のエスケープ
U+2028 と U+2029
+ を \u002B
  • XSRF

    アクセスの際にXSRFトークンをあらかじめ渡しておき、 それをパラメータとして送ってこなかった場合にはアクセスを拒否する X-Requested-Withをチェックして、存在していなければアクセスを許可しないようにする

  • JSONハイジャック用コード

<script type="application/javascript">
     var data;
     Array = function() {
       data = this;
     };
</script>

<script type="application/javascript">
       Object.prototype.__defineSetter__('id', function(obj){alert(obj);});
</script>

<script>
    window.onerror = function(e) {}
</script>
<script src="https://api.example.com/v1/users/me" language="vbscript"></script>
  • JSONハイジャックの対策
XMLHttpRequest or ブラウザ以外からのアクセスのみ
→X-Requested-Withがない場合はアクセスを許可しない

レスポンスヘッダー
→Content-Type: application/json
→X-Content-Type-Options: nosniff

トップレベルに配列を使用しない

JSONの先頭にfor (;;);
→Javascriptとして実行されても次にいけなくしている
※facebook
  • リクエストの再送信
同じアクセスが何度も行われたらエラーにする

1回の購入につき1回のポイントの付与だけを行うようにチェック
→購入トークンの発行とサーバ側での保持、検証
  • ヘッダー
Content-Type: application/json
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
X-Frame-Options: deny
Content-Security-Policy: default-src 'none'
Strict-Transport-Security: max-age=15768000
Public-Key-Pins: max-age=2592000;
          pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=";
          pin-sha256="LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="
Set-Cookie: session=e827ea0c0fe8c109eb37a60848b5ed39; Path=/; Secure; HttpOnly
  • ヘッダーの例
HTTP/1.1 200 OK
Date: Mon, 16 Jun 2014 21:39:06 GMT
Server: nginx
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: *
Tracer-Time: 43
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 499
Strict-Transport-Security: max-age=864000
X-ex: fastly_cdn
Content-Length: 11074
Accept-Ranges: bytes
Via: 1.1 varnish
X-Served-By: cache-ty66-TYO
X-Cache: MISS
X-Cache-Hits: 0
Vary: Accept-Encoding,User-Agent,Accept-Language
HTTP/1.1 200 OK
Server: GitHub.com
Date: Mon, 16 Jun 2014 21:32:36 GMT
Content-Type: application/json; charset=utf-8
Status: 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 55
X-RateLimit-Reset: 1402957018
Cache-Control: public, max-age=60, s-maxage=60
Last-Modified: Mon, 16 Jun 2014 04:55:23 GMT
ETag: "cbd0cecf6295eba60adc4c06c7836b8d"
Vary: Accept
X-GitHub-Media-Type: github.v3
X-XSS-Protection: 1; mode=block
X-Frame-Options: deny
Content-Security-Policy: default-src 'none'
Content-Length: 1201
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit- Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
Access-Control-Allow-Origin: *
X-GitHub-Request-Id: 719794F7:01FB:299F044:539F6273 Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff
Vary: Accept-Encoding
X-Served-By: 971af40390ac4398fcdd45c8dab0fbe7
• リミット値の設定
エンドポイント毎: 15分 15回(ユーザー)/180回(アプリ)
APIの種類(core,search等)毎: 1時間5000回/60回
※アクセス制限の緩和も想定しておく
  • 上限オーバーしたとき
HTTP/1.1 429 Too Many Requests
Retry-After: 3600
  • レスポンスヘッダーにレートリミットを渡す
X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset
  • レートリミットの実装
redisにする

ドキュメント

GitHub - subosito/iglo: API blueprint's formatter

https://developers.facebook.com/tools/explorer/

console.developers.google.com

AWSにおけるインフラの依存関係について整理してみる

AWSのリソースと各依存先をまとめてみます
以下が頭に入っているといないではインフラスキルは段違いだと思います。

IAM,S3

すべての始まり

VPC

IAMに依存
※VPCFlowLogのDeliverLogsPermissionArn

SG

VPCに依存
※SGはVPC内に存在

ELB

VPCに依存
※ELBはサブネットを指定する必要があるため、その前提のVPCに依存

SGに依存
※ELB用のSG

AutoScaling

VPCに依存
※AutoScalingはサブネットを指定する必要があるため、その前提のVPCに依存

ELBに依存
※どのELBにAutoScalingをぶら下げるかの指定に必要

SGに依存
※インスタンス用のSGに依存

IAMに依存
※インスタンスがIamInstanceProfileに依存

CodeDeploy

IAMに依存
※CodeDeployからAutoScalingへのアクセス等に依存

AutoScalingに依存
※デプロイ先

Route53

ELBに依存
DBに依存
ElasticCacheに依存
...
※その他必要に応じて増減

Lambda

S3に依存
※ソース置き場

DynamoDB
※データ格納先

ApiGateway

IAMに依存
※ApiGatewayからCloudWatchへのアクセスに必要

Lambdaに依存
※Integration先

AutoScalingグループを作成する場合の注意点について

AutoScalingグループを作成する際の注意点について、まとめていきます。 特に言及していないプロパティはAWSの例通りでいいと思います。

AutoScalingGroupのプロパティ

インスタンスの数(MinSize,MaxSize,DesiredCapacity)

AZの倍数にする
東京なら1aと1cなので、2の倍数にする

※ELBの基本としてAZのインスタンス数は均等にすることが推奨されているため
http://docs.aws.amazon.com/ja_jp/ElasticLoadBalancing/latest/DeveloperGuide/enable-disable-crosszone-lb.html

耐障害性を高めるために有効な各アベイラビリティーゾーンにおよそ等しい数のバックエンドインスタンスを維持することをお勧めします。

HealthCheckGracePeriod

新しい EC2 インスタンスがサービス状態になってから、Auto Scaling がヘルスチェックを開始するまでの秒数。
デフォルトは300ですが、できるだけ早くヘルスチェックを開始しないと高負荷時にサービスが停止する可能性が高くなります。
そのため、起動時のプロビジョニングはできるだけしないようにして、プロビジョニング済みのAMIを使用すべきです。

※スケーリング時のインスタンス追加までの流れはおおまに以下のようになります

alarm(Threshold超えから次回のPeriod(最小60)まで): 60-α秒
インスタンス起動からサービス状態になる: 約3~10分
ELBのヘルスチェックのInterval(最小は5) * HealthyThreshold(最小は2): 最小5秒* 2
HealthCheckGracePeriod経過: 最小はなしだが、必ずヘルスチェック成功までの時間以上にする

つまり耐障害性を高めるためには以下のようにすべきです

* Periodを60
* 起動時のプロビジョニングはできるだけしない
* ヘルスチェックの成功までの時間をできるだけ短くする
* HealthCheckGracePeriodをできるだけ小さくする
* (ELBのヘルスチェック成功直後にAutoScalingGroupがヘルスチェックをする時間にする)

HealthCheckType

${デプロイ時間} > Interval * HealthyThresholdの場合はELBだとデプロイ時にインスタンスが増えます。
ELBにするとアクセスが集中して,ELBのヘルスチェック失敗時にインスタンスを増やしてくれます。

Tags

以下のようにして、オートスケーリンググループ内で起動したインスタンスに名前が設定されるようにします

        "Tags": [
          {
            "ResourceType"      : "auto-scaling-group",
            "ResourceId"        : "AutoScalingGroup",
            "PropagateAtLaunch" : true,
            "Key"               : "Name",
            "Value"             : { "Ref" : "AWS::StackName" }
          }
        ]

LaunchConfig

できるだけ、UserDataはシンプルにします。  
また、cfn-initでの各種インストールはAMIでしておくべきです。  
最後に成功のシグナルをCloudFormationが受け取れるようにcfn-signalを実行します。  
        "UserData": { "Fn::Base64" : { "Fn::Join" : ["", [
          "#!/bin/bash -xe\n",
          "# Install the files and packages from the metadata\n",
          "/opt/aws/bin/cfn-init",
          " --stack ", { "Ref" : "AWS::StackName" },
          " --resource LaunchConfig",
          " --configsets Install",
          " --region ", { "Ref" : "AWS::Region" }, "\n",

          "# Signal the status from cfn-init\n",
          "/opt/aws/bin/cfn-signal -e $?",
          " --stack ", { "Ref" : "AWS::StackName" },
          " --resource AutoScalingGroup",
          " --region ", { "Ref" : "AWS::Region" }, "\n"
        ]]}}

CreationPolicy

CountはUpdatePolicyのMinInstancesInServiceと同じ値を使用します。  
TimeoutはPT5Mにします。  
起動に5分以上かかる場合は実際に負荷の上昇にインスタンスの増加が間に合わなくなる可能性があります  
複数台でも同時に作成されます
※通常約3分でReceived SUCCESS signal with UniqueId i-XXXXXXXXXXXXXXXXXXXXになります
      "CreationPolicy": {
        "ResourceSignal": {
          "Count"  : "X",
          "Timeout": "PT5M"
        }
      }

UpdatePolicy

CloudFormationがAutoScalingGroupに対するUPDATEの設定です。
※AutoScaling時の設定ではありません

WillReplace

AutoScalingに対する設定で最も重要な設定です。

trueにすることで作成に成功したときしか前のAutoScalingGroupを削除しません
※trueにしない場合はスタックのロールバックに失敗する可能性があります

PauseTime

CreationPolicyのTimeoutと同じでシグナルを受け取るまでなので、5分にします。
※これでシグナル送信までに5分以上経過するとUpdateは失敗します
      "UpdatePolicy" : {
        "AutoScalingReplacingUpdate" : {
          "WillReplace" : "true"
        },
        "AutoScalingScheduledAction" : {
          "IgnoreUnmodifiedGroupSizeProperties" : "true"
        },
        "AutoScalingRollingUpdate" : {
          "MinInstancesInService" : { "Fn::FindInMap": [ "InstanceSettings", "Count", "Min"]},
          "MaxBatchSize"          : "X",
          "WaitOnResourceSignals" : "true",
          "PauseTime"             : "PT5M"
        }
      }

Outputs

AutoScalingGroupを差し替える(新グループの作成後に旧グループの削除)際にCodeDeployのデプロイ先のAutoScalingGroupも自動的に更新されるようにします。

  "Outputs" : {
    "AutoScalingGroup" : {
      "Value" : { "Ref" : "AutoScalingGroup" }
    }
  }

テンプレート

    "AutoScalingGroup" : {
      "Type" : "AWS::AutoScaling::AutoScalingGroup",
      "Properties" : {
        "LaunchConfigurationName" : { "Ref" : "LaunchConfig" },
        "MinSize"                 : "X",
        "MaxSize"                 : "X",
        "DesiredCapacity"         : "X",
        "HealthCheckGracePeriod"  : "XX",
        "HealthCheckType"         : "ELB",
        "LoadBalancerNames"       : [ { "Ref" : "ELBWeb" } ],
        "VPCZoneIdentifier"       : [
          { "Ref": "PublicSubnet1a" },
          { "Ref": "PublicSubnet1c" }
        ],
        "Tags": [
          {
            "ResourceType"      : "auto-scaling-group",
            "ResourceId"        : "AutoScalingGroup",
            "PropagateAtLaunch" : true,
            "Key"               : "Name",
            "Value"             : { "Ref" : "AWS::StackName" }
          }
        ]
      },
      "CreationPolicy": {
        "ResourceSignal": {
          "Count"  : "${MinInstancesInService}",
          "Timeout": "PT5M"
        }
      },
      "UpdatePolicy" : {
        "AutoScalingReplacingUpdate" : {
          "WillReplace" : "true"
        },
        "AutoScalingScheduledAction" : {
          "IgnoreUnmodifiedGroupSizeProperties" : "true"
        },
        "AutoScalingRollingUpdate" : {
          "MinInstancesInService" : "${MinInstancesInService}",
          "MaxBatchSize"          : "X",
          "WaitOnResourceSignals" : "true",
          "PauseTime"             : "PT5M"
        }
      }
    }

Unable to determine service/operation name to be authorized

解決策

IntegrationHttpMethodをPOSTにする

    "MockMethod": {
      "Type": "AWS::ApiGateway::Method",
      "Properties": {
        "ApiKeyRequired" : "false",
        "AuthorizationType": "NONE",
        "HttpMethod": "GET",
        "Integration": {
          "IntegrationHttpMethod" : "POST",
          "Type": "AWS",
          "Uri" : {"Fn::Join" : ["", [
            "arn:aws:apigateway:",
            { "Ref" : "AWS::Region" },
            ":lambda:path/2015-03-31/functions/",
            {"Ref" : "LambdaFunctionArn" },
            "/invocations"
          ]]}
        },
        "ResourceId": { "Fn::GetAtt": ["RestApi", "RootResourceId"] },
        "RestApiId": { "Ref": "RestApi" }
      },
      "DependsOn" : "RestApi"
    }

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

CloudFrontでaws cliからOrigin Access Identityを作成する

コマンドと結果

aws configure set preview.cloudfront true
aws cloudfront create-cloud-front-origin-access-identity --cloud-front-origin-access-identity-config CallerReference=${現在日時のタイムスタンプ},Comment=XXXX
{
    "Location": "https://cloudfront.amazonaws.com/2016-01-28/origin-access-identity/cloudfront/XXXXXXXXXXXXXX",
    "CloudFrontOriginAccessIdentity": {
        "Id": "XXXXXXXXXXXXXX",
        "CloudFrontOriginAccessIdentityConfig": {
            "CallerReference": "現在日時のタイムスタンプ",
            "Comment": "XXXX"
        },
        "S3CanonicalUserId": "XXXXXXXXXXXXXXXXXXX"
    },
    "ETag": "XXXXXXXXXXXXXX"
}

参考

create-cloud-front-origin-access-identity — AWS CLI 1.10.47 Command Reference

CloudFormationによるVPC Flow Logs

AWS CloudFormation Adds Support for Amazon VPC Flow Logs, Amazon Kinesis Firehose Delivery Streams, and Other Updates

AWSさんがCloudFormationによるFlow Logsをサポートしたようです。 しかし、よいテンプレートがないので作ってみました。

FlowLogsRole

まずはvpc-flow-logs.amazonaws.com用にロールを作成します。

    "FlowLogsRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "vpc-flow-logs.amazonaws.com"
                ]
              },
              "Action": [ "sts:AssumeRole" ]
            }
          ]
        },
        "Path": "/"
      }
    }

FlowLogsPolicy

次にRoleにPolicyを紐付けます。

    "FlowLogsPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "FlowLogs",
        "PolicyDocument": {
          "Version" : "2012-10-17",
          "Statement": [
            {
              "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "logs:DescribeLogGroups",
                "logs:DescribeLogStreams"
              ],
              "Effect": "Allow",
              "Resource": "*"
            }
          ]
        },
        "Roles": [
          { "Ref": "FlowLogsRole" }
        ]
      },
      "DependsOn" : [
        "FlowLogsRole"
      ]
    }

FlowLog

最後にVPCに対してFlowLogを作成します。

    "VPCFlowLog": {
      "Type" : "AWS::EC2::FlowLog",
      "Properties" : {
        "DeliverLogsPermissionArn" : { "Ref" : "FlowLogsRoleArn" },
        "LogGroupName"             : { "Fn::Join": ["", [
          "VPCFlowLogsGroup", "-", { "Ref": "AWS::StackName" }
        ]]},
        "ResourceId"               : { "Ref" : "VPC" },
        "ResourceType"             : "VPC",
        "TrafficType"              : "ALL"
      }
    }

VPC毎のロググループ名を分けるためにスタック名を使用します。 ※FlowLogsRoleArnは{ "Fn::GetAtt" : [ "FlowLogsRole", "Arn" ] }です。

Supervisor(GolangのWEBアプリ)をインスタンス起動時に自動起動させてみる

GolangのアプリケーションのためのSupervisorは何を使って起動するかについてですが、 cfn-initやCodeDeployではフォアグラウンド起動ができません。 Amazon Linux AMIにはupstartの仕組みがすでに備わっていますので、そちらを使ってみます。

/etc/init/supervisord.conf

start on runlevel [2345]
stop on runlevel [!2345]
limit nofile 200000 200000

exec supervisord -c /etc/supervisor/conf.d/supervisord.conf --nodaemon

起動してみる

  • initctlを確認してみる
# initctl list
supervisord start/running, process 1834
  • プロセスを確認してみる
XXXX      1834  0.0  1.6 114628 17160 ?        Ss   08:54   0:00 /usr/bin/python2.7 /usr/local/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf --nodaemon
XXXX      2994  0.0  0.6  31500  6540 ?        Sl   09:13   0:00 /var/app/bin/reserve

これは楽ちんですね。
Node.jsのアプリでも使えるかもしれません。

参考

Supervisor: A Process Control System — Supervisor 3.3.0 documentation

AWS Elastic Beanstalk ドキュメント | AWS

ログインなしでサーバー運用するには

参考

speakerdeck.com

※上記はOpsWorksを使用している点であまりオススメはできないですが、参考として置いておきます。

サーバーのログインの面倒くさい点・問題点

ユーザー追加、管理
誰が何をしたか、するかが可視化されない
手動デプロイによるオペミス
手動デプロイした内容がおかしい
手動操作によってサーバー毎に差が出る
踏み台サーバーの構築、サーバー管理
NACL,SGにSSHの穴が開いてしまう

ログインなしで済むようにするには

  • cfn-hupを使用する
一律サーバーに何かをする場合はCloudFormationのMetadataの更新で実行する
※サーバーに何をするかはGitHub上のdiffで可視化する

docs.aws.amazon.com

  • AWS CodeDeployとGitHubの連携でデプロイする
プルリクマージボタンを押すだけでデプロイされるようにする
※デプロイに必要な操作はクリックだけにする

手動操作にオペミスが発生する仕組みかどうか

踏み台サーバーがあるかどうか
NACL,SGにSSHの記載があるかどうか
サーバーにユーザー追加などをしているかどうか
デプロイにログインが必要かどうか
操作に関するドキュメントが大量にあるかどうか
インフラがソース管理されていないかどうか

AWS CodeDeployとGitHub Auto-DeploymentとAutoScalingを連携させてみる

やること

GitHubでプルリクがマージされたら、
自動的にCodeDeployがAutoScalingグループにデプロイしてくれるようにする

前提

EC2にCodeDeployエージェントがインストール済み
GitHubユーザーが作成済みでAWSCodeDeployRoleがアタッチされている
DeploymentGroupが作成済み
GitHub上でPersonal access tokens(repo_deploymentのみチェック)が作成済み

GitHub

  • Services(Add AWS CodeDeploy)
設定項目 設定値
Application name ${アプリケーション名}
Deployment group ${CodeDeployで作成したDeploymentGroup}
Aws access key ${IAMのGItHubユーザーのもの}
Aws region ap-northeast-1
Aws secret access key ${IAMのGItHubユーザーのもの}
GitHub token ${GitHub上で作成したPersonal access token}
※Scopeはrepo_deploymentのみ
  • Services(Add GitHub Auto-Deployment)
設定項目 設定値
GitHub token ${GitHub上で作成したPersonal access token}
※Scopeはrepo_deploymentのみ
Environments ${AWS Code Deployのデプロイグループ名}

最後に

あとはGitHubGitHub Auto-Deploymentにグリーンのチェックが付いていれば、設定が完了していますので、 プルリクをマージ or pushすれば自動的にデプロイしてくれます。

f:id:keiwt:20160630203641p:plain