AWS AppSyncとReactでToDoアプリを作ってみよう (2)DataSourceとResolverの設定

はじめに

前回の記事で定義したGraphQLスキーマはAPIのインターフェースの定義でした。
このインターフェースを使ってAPIにアクセスがあった際に、データを保存する先のリソース(DataSource)の作成と連携の部分を設定していきます。
AWS AppSyncでは、Resolverを作成することで、DataSourceとGraphQLスキーマとの紐付けを行います。

リソース(DataSource)の作成

今回の例では、GraphQLスキーマに定義したTodo型からDynamoDBのテーブルを作成します。
まずは、コンソール画面から、「AWS AppSync > 作成したプロジェクト > Schema」を開き、画面右上の「Create Recources」をクリックします。

CreateRecouces.png

次の画面では、定義済みのGraphQLスキーマから使用する型を選択します。
今回はTodoを選択しました。

スクリーンショット 2018-01-22 6.07.41.png

使用する型を選択すると、自動でテーブル名、テーブルの構成が入力されます。
今回は、idをプライマリキーとしました。

スクリーンショット 2018-01-22 7.05.49.png

ここまでの項目を入力すると、画面最下部にテーブル定義を元に、自動で追加されるスキーマ定義のプレビューが表示されます。
最後に、「Create」ボタンをクリックすると、DynamoDBテーブルが作成されます。

スクリーンショット 2018-01-22 7.05.59.png

Resolverの追加と修正

前の手順では、GraphQLスキーマから自動でDynamoDBとResolverのプロビジョニングを行いましたが、一部、定義元のスキーマと紐付いていない点があるので、追加・修正していきます。

getTodos

getTodosには、Resolverが紐付けられていないので、新規で追加します。

「Schema > Query > getTodos: [Todo]」の「Attach」をクリックします。
次の画面では、作成済みのDataSourceから紐付けるテーブルを選択します。
今回はTodoTabeを選択しました。

スクリーンショット 2018-01-22 6.42.08.png

次に、リクエストのマッピングを行います。
「Paginated scan」のテンプレートをベースに、次のように変更しました。

RequestMappingTemplate
{
    "version" : "2017-02-28",
    "operation" : "Scan"
}

レスポンスのマッピングは次のようになります。

ResponseMappingTemplate
$utils.toJson($context.result.items)

最後に、画面右下の「Save」をクリックしてResolverの設定を保存します。

getTodo

これは、自動で追加されたQueryです。
今回のToDoアプリでは、item一件ごとに取得するGetItem操作は不要なので、削除します。

allTodo

これは、自動で追加されたQueryです。
こちらは、getTodosと処理が重複するため削除します。
(ページネーション処理も自動で設定済みのようなので、こちらを使うのがよさそうですが…)

addTodo

自動で追加されたMutation、putTodoの内容をこちらに移行すれば良さそうです。

RequestMappingTemplate
{
    "version" : "2017-02-28",
    "operation" : "PutItem",
    "key": {
        "id": { "S" : "${context.arguments.id}"}
    },
    "attributeValues" : {
        "id": {  "S": "${context.arguments.id}" },
        "title": {  "S": "${context.arguments.title}" },
        "description": {  "S": "${context.arguments.description}" },
        "completed": {  "B": "${context.arguments.completed}" }
    }
}
ResponseMappingTemplate
$utils.toJson($context.result)

updateTodo

updateTodoには、Resolverが紐付けられていないので、新規で追加します。
getTodosで行った新規追加手順と同様に操作を行い、次のテンプレートを設定します。

RequestMappingTemplate
{
    "version" : "2017-02-28",
    "operation" : "UpdateItem",
    "key": {
        "id": { "S" : "${context.arguments.id}"}
    },
    "update" : {
        "expression" : "SET title = :title, description = :description, completed = :completed",
        "expressionValues" : {
            ":title" : { "S": "${context.arguments.title}" },
            ":description" : { "S": "${context.arguments.description}" },
            ":completed" : { "BOOL": ${context.arguments.completed} }
       }
    }
}
ResponseMappingTemplate
$utils.toJson($context.result)

更新操作は、DynamoDBの更新式の指定が必須となっているようでうです。

deleteTodo

こちらは、自動でしっかりとマッピングされているので、そのまま使用します。

putTodo

これは、自動で追加されたMutationです。
こちらは、addTodoと処理が重複するため削除します。

以上で、Resolverの設定が終わりました。

APIの動作確認

作成したAPIの動作確認を行ってみます。
コンソール画面から、「AWS AppSync > 作成したプロジェクト > Queries」を開き、画面左側のエディターエリアにクエリを入力し「▶」をクリックしてクエリを実行します。

スクリーンショット 2018-01-22 22.37.05.png

MutationとQueryそれぞれの項目を動作確認してみたいと思います。

addTodo

まずは、データを追加してみます。

Query
mutation addTodo {
  addTodo(
    id: "0651ed86-9314-4267-9bcf-7143b785f173"
    title: "髪を切る"
    description: "来週までには"
    completed: false
  ) {
    id
    title
    description
    completed
  }
}

次のようなレスポンスが返ってくれば成功です。

Response
{
  "data": {
    "addTodo": {
      "id": "0651ed86-9314-4267-9bcf-7143b785f173",
      "title": "髪を切る",
      "description": "来週までには",
      "completed": false
    }
  }
}

getTodos

事前に何件かデータを追加した状態で以下のクエリを実行します。

Query
query {
  getTodos {
    id
    title
    description
    completed
  }
}
Response
{
  "data": {
    "getTodos": [
      {
        "id": "f163372a-8b54-4da4-9237-911a64067517",
        "title": "豆腐を食べる",
        "description": "腐りそう",
        "completed": false
      },
      {
        "id": "0cbab86a-ad72-41b4-a63d-9ce3f9a7d552",
        "title": "Qiita書く",
        "description": "あと2本",
        "completed": false
      },
      {
        "id": "0651ed86-9314-4267-9bcf-7143b785f173",
        "title": "髪を切る",
        "description": "来週までには",
        "completed": false
      }
    ]
  }
}

updateTodo

Query
mutation updateTodo {
  updateTodo(
    id: "0651ed86-9314-4267-9bcf-7143b785f173"
    title: "部屋を掃除する"
    description: "さらっと済ます"
    completed: false
  ) {
    id
    title
    description
    completed
  }
}

※ Responseは省略

deleteTodo

Query
mutation deleteTodo {
  deleteTodo(
    id: "0651ed86-9314-4267-9bcf-7143b785f173"
  ) {
    id
    title
    description
    completed
  }
}

対象のitemのidを指定して、削除を行います。

※ Responseは省略

最後に

今回は、定義したGraphQLスキーマから自動でリソースを作成しましたが、AppSyncのコンソールからは、全て手動でリソースを用意することもできます。必要に応じて使い分けると良さそうです。
次回は、作成したGraphQL APIと連携するReactフロントエンドの実装を行っていく予定です。

参考

Attaching a Data Source -AWS AppSync
Provision from Schema -AWS AppSync
Resolver Mapping Template Reference for DynamoDB -AWS AppSync

続きを読む

IDCFオブジェクトストレージ AWS CLI エラー 【The authorization mechanism you have provided (AWS4-HMAC-SHA256) is not supported.】

現象

IDCFオブジェクトストレージ向けの AWS CLI S3 コマンドがエラーを吐く

$ aws --endpoint-url https://ds.jp-east.idcfcloud.com s3 ls
An error occurred (InvalidRequest) when calling the ListBuckets operation: The authorization mechanism you have provided (AWS4-HMAC-SHA256) is not supported.

原因

リクエスト認証には、v2とv4があり最新のawsの認証はデフォルトでv4になっている。
しかし、IDCFオブジェクトストレージの認証はv2になっており、v4認証でアクセスするとエラーとなる。

対策

リクエスト認証での署名バージョンの指定が可能なので、v4からv2に変更する。

$ aws configure set default.s3.signature_version s3

ちなみにv4を指定する場合はこう

$ aws configure set default.s3.signature_version s3v4

参考リンク

http://docs.openio.io/master/source/user-guide/awscli.html

続きを読む

Amazon SageMakerでml.p2.xlargeを使おうとしたらResourceLimitExceededって怒られた

事象

Amazon SageMakerで ml.p2.xlarge を使って学習させようとしたら以下のエラーが出て学習ができなかった。

ResourceLimitExceeded: An error occurred (ResourceLimitExceeded) when calling the CreateTrainingJob operation: 
The account-level service limit for training-job/ml.p2.xlarge is 0 Instances, with current utilization of 0 Instances and a request delta of 1 Instances. 
Please contact AWS support to request an increase for this limit.

原因

AWSでは利用できるリソースに制限があります。
詳しくはこちらのページをご確認ください。

Amazon SageMakerにも制限があり、こちらでは ml.p2.xlarge インスタンス のデフォルトの制限は 1 となっています。

対策

AWSのサポートに連絡して制限を緩和してもらいましょう。

  1. AWSのコンソールにログイン
  2. 右上の サポート から サポートセンター のページを表示
  3. ケースの作成 を押下
  4. 内容で サービス制限の増加 を選択
  5. 制限タイプで SageMaker を選択
  6. リージョン、リソースタイプ、制限をそれぞれ選択
  7. 新しい制限値を入力
  8. 適当に理由を書いて 送信 を押下

1営業日程度で対応してくれます。
海外のリージョンの申請は海外の担当部署と調整が必要らしく、少し時間がかかるかも

他のサービスも色々制限があったりするので、申請方法を覚えておきましょう。

続きを読む

AWSの料金支払い漏れでアカウントロックされた話

個人で使っていたAWSアカウントで支払い漏れが2か月続いたためアカウントロックされてしまいました。
その解除手順を載せておきます。

なぜ支払い漏れが発生していたのか

結婚をし、自分の苗字が変わりました。
苗字が変わると色々大変で、クレジットカードも再発行になります。
クレカ番号が変わるわけなので、クレカ情報を登録しているサービスは全部登録しなおしです。

一通り利用していたサービスは登録しなおしたつもりでした。
当たり前ですが、AmazonとAWSはクレカの情報を共有しているわけではありません。
Amazonでクレカ変更してやった気になってAWSのクレカ変更を忘れて今に至りました…。

メールでお知らせも来てたはずですが、それも見逃してました。

どうやって検知したのか

AWSが使えなくなったのがわかったのが以下の記事の最終チェックをしていた時でした。
sshログイン時にAWSの料金を表示させる

AWS料金ネタをやってたときに支払い漏れが発生したのが笑えます。

で、AWS CLIでCloudWatchのGetMetricsが使えなくなってました。
AWS CLIでは以下のようなエラーが出てました。

An error occurred (InvalidClientTokenId) when calling the GetMetricStatistics operation: The security token included in the request is invalid.

最初はIAMを間違えて消してしまったのか?とかアカウント乗っ取られた?そんなバカな…と思って
AWSにログインしてみると…

aws_billing.png

Amazon Web Services Sign In
This AWS account is not accessible. If you just signed up with AWS, please try again at a later time.

Please contact our customer service team for assistance.

!?!?!?!?!?!?!?!?!

この画面からサポート画面に飛ぶことができ、ログインできているのは確認できます。
ここで「もしかしたら支払い漏れてるんじゃね?」と気づき、支払い履歴を見たら

aws_billing2.png

すみませんでしたーーーーっ!!!

対処方法

上記画面の下に「今すぐ支払う」みたいなボタンがあるので
それを押すとクレジットカード選択画面が出るので、新しいクレカを登録して支払いして終了です。

その後すぐにAWSサービスを使るようになりました。
もちろんIAMのアクセス権も正常になり、AWS CLIでCloudWatchのメトリクスを取れるようになりました。

最後に

ちなみにアカウントロック中でもLightsailのサーバーには入れました。
サーバー自体に入れなくなることがあるのかわかりませんが、IAMが使えなくなる時点で結構やばいです。

今回は個人のアカウントだったのでなんともなかったですが、
企業アカウントでこれをやらかすとなにかしらサービスに影響があります。

あと個人でも企業でも支払い漏れはダメ絶対。こういうところも監視しないとだめだなぁと改めて思いました。

続きを読む

GoでZabbixを爆速にしたかった

はじめに

こんにちは。
CYBIRDエンジニア Advent Calender 2017の22日目は@gucchonさんのゲーム開発をWebViewからUnityに乗り換えて、苦労したこと、良かったことでした。

本日の記事のあらすじ・動機

  • タイトルからもお察しかと思いますが, 失敗談になります。
  • 弊社ではサーバの監視にZabbixを使用しております。(一部はmackerelを使用させていただいております。)
  • Zabbixを容易に拡張する手段として”UserParameter”と”外部スクリプト”が存在します。
    • しかし, これらはメトリクスの取得の度にプロセスをforkするので負荷がかかります。
    • 外部スクリプトの過度の使用によってパフォーマンスが劣化することはZabbixのドキュメントに明記されております。
    • 弊社(のAWSを使用した案件)では外部スクリプトに頼った監視になっており, 監視サーバのインスタンスタイプが本番環境のWebサーバのインスタンスタイプを超えているケースがあったりします。
  • Zabbixサーバの負荷をなんとか抑えたいということが今回の動機になります。
  • Zabbixモジュールを使用することで, ネイティブ実装と同じパフォーマンスを得ることができる。
    • PHPのCGIモードとmoduleモードでパフォーマンスが異なる理由と同じになります。
    • UserParameter・外部スクリプトを使用するときのようなオーバーヘッド(fork)が削減されます。
    • ネイティブ実装と同じパフォーマンスを得ることができる。
    • Zabbixサーバのインスタンスタイプを落とすことが出来るかもしれない。
    • しかしZabbixモジュールを実装するためには基本的にはCで実装する必要があります。
  • そこでGolangで気軽にZabbixモジュールを作成して使用してみました。
    • しかし後述する, “ZabbixとGoランタイムの相容れないアーキテクチャ”によって失敗するという話になります…
  • 着想・実装にあたっては, @ike_daiさんの記事Golangを使ってZabbixを拡張大いに参考にさせていただいております。

実際にやってみた結果…

実装

  • 変数等のネーミングセンスがない件については, ご容赦ください。

    • AWSのCredentialを.envファイルから読み込んでおります。
    • github.com/cavaliercoder/g2zを使用させていただいております。
    • 既に作者様が行ったパフォーマンスのテストの結果がperformance.mdに記載されております。
mod_zabbix_go.go
package main

import (
    "C"
    "errors"
    "log"
    "strings"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/credentials"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/ec2"
    "github.com/joho/godotenv"
    g2z "gopkg.in/cavaliercoder/g2z.v3"
)

// main ... 共有ライブラリなので通常は実行されない
func main() {
    panic("THIS_SHOULD_NEVER_HAPPEN")
}

// env_load ... pathに存在する.envを読み込み環境変数にマッピング
func env_load(path string) {
    err := godotenv.Load(path)
    if err != nil {
        log.Fatalf("Error loading .env file in %v.n", path)
    }
}

// init ... 共有ライブラリ読み込み時に実行される
func init() {
    env_load("/etc/zabbix/aws.env") // AWS_ACCESS_KEY_IDとAWS_SECRET_ACCESS_KEYの値を参照
    g2z.RegisterStringItem("go.echo", "Hello World", Echo)
    g2z.RegisterStringItem("go.ec2_name2id", "error", Ec2Name2Id)
}

// Echo ... 引数の値を全て連結して返す
func Echo(req *g2z.AgentRequest) (string, error) {
    return strings.Join(req.Params, " "), nil
}

// Ec2Name2Id ... EC2インスタンスのタグ名からインスタンスIDを取得
func Ec2Name2Id(req *g2z.AgentRequest) (string, error) {
    if len(req.Params) != 1 {
        return "", errors.New("can use only 1 argument(s).")
    }

    sess, err := session.NewSession(&aws.Config{
        Credentials: credentials.NewEnvCredentials(),
    })
    if err != nil {
        return "error", err
    }

    svc := ec2.New(
        sess,
        aws.NewConfig().
            WithRegion("ap-northeast-1").
                WithLogLevel(
                    aws.LogDebugWithRequestRetries |
                    aws.LogDebugWithRequestErrors  |
                    aws.LogDebugWithHTTPBody,
                ),
    )

    params := &ec2.DescribeInstancesInput{
        Filters: []*ec2.Filter{
            &ec2.Filter{
                Name: aws.String("tag:Name"),
                Values: []*string{
                    aws.String(req.Params[0]),
                },
            },
        },
    }

    res, err2 := svc.DescribeInstances(params)
    if err2 != nil {
        return "error", err2
    }

    for _, r := range res.Reservations {
        for _, i := range r.Instances {
            return *i.InstanceId, nil
        }
    }
    return "empty", nil
}

ビルド環境

  • AWSのCodeBuildを使用してビルドしております。CircleCI?知らない子ですね…

    • Dockerイメージはgolang:1.9.2を使用してビルドしております。
buildspec.yml
version: 0.2

phases:
  install:
    commands:
      - wget https://github.com/Masterminds/glide/releases/download/v0.13.1/glide-v0.13.1-linux-amd64.tar.gz
      - tar xvf glide-v0.13.1-linux-amd64.tar.gz
      - install -o root -g root -m 0755 linux-amd64/glide /usr/local/bin/glide
  pre_build:
    commands:
      - /usr/local/bin/glide install
  build:
    commands:
      - GOOS=linux GOARCH=amd64 go build -buildmode=c-shared -o mod_zabbix_go.so main.go

artifacts:
  files:
    - 'mod_zabbix_go.so'
  discard-paths: no

環境設定

  • Moduleを読み込むよう設定を記述し, Zabbix Agentを起動・再起動します。
/etc/zabbix/zabbix_agentd.conf
+ LoadModulePath=/var/lib/zabbixsrv/modules
+ LoadModule=mod_zabbix_go.so
systemctl restart zabbix-agent.service

動作確認

  • 実際に確認してみますと, リクエストを飛ばすところでハングっているようです…
$ zabbix_get -s localhost -k 'go.echo[hello, ntrv]'
hello ntrv # OK!!
$ zabbix_get -s localhost -k 'go.ec2_name2id[ntrv-test]'

zabbix_get [24960]: Timeout while executing operation # i-xxxxxxと返ってくることを期待していた...

失敗した原因について

  • マルチスレッドのgoランタイムでプロセスを安全にフォークすることは不可能

    • 一般的にシングルスレッドのプログラムがマルチスレッドとなる共有ライブラリをdlopen()で呼び出した段階でforkすることが出来ない。
    • https://github.com/golang/go/issues/15538
  • 共有ライブラリをロードしたプロセスがfork()を呼び出した後, 子プロセスでGo codeを使用できない。
    • エージェントがforkする際, Copy-on-WriteでメモリマッピングをコピーするがGoランタイムスレッドをコピーしてくれない。

      • 結果, g2zで記述した関数が呼び出されると, 子プロセスに存在しないスレッドを使用しようとしてデッドロックに陥る?
    • 詳しい方, 教えて頂けるとありがたいです…

References

Zabbix負荷問題を軽減する上で考えうる別の解決方法

  • 失敗してしまったので
  1. Zabbixをやめる
  2. EC2のCloudWatchメトリクスを取得する場合には, 外部スクリプトではなくUserParameterを使用する。
    • Zabbixサーバに負荷が集中するのを防ぐため。
    • RDS等ZabbixAgentが使用出来ない場合に外部スクリプトを使用する。
  3. Zabbix Senderを使用する。
    • 別途エージェントを用意してそこでメトリクスを収集し, ZabbixサーバのZabbix Trapperに投下する。
    • 弊社ですとHTTPステータスコードやアクセス数を, Webサーバ上のtd-agentからZabbix Trapperに送っております。

最後に

今回はZabbixを爆速化しようとして失敗してしまったことをまとめました。
GoでZabbix Sender専用のエージェントを用意して, エージェント上でAWS APIの結果を収集しZabbixサーバに送ってもよさそうですね。
時間があるときに試してみようかと思います。

また今回の経験を通して, Goのことというよりはマルチスレッドプログラミングが分かっていなかったということを痛感いたしました。Golangの文法が簡単だからと言ってなめておりました。
そのあたりの知識を身に着けなければなと考えております。

CYBIRDエンジニア Advent Calendar 2017の24日目は@koki_yamadaさんの”Unityのコード編集にVimを使ってみた”です!
私の冬休みの宿題はneobundle.vim/neocomplete.vimdein.vim/deoplete.nvimに置き換えることなので参考にしようかと思います。
どうぞお楽しみに!

続きを読む

シェルシェルのAWS運用術

この記事は「OpsJAWS Advent Calendar 2017」と「Shell Script Advent Calendar 2017」の 18日目の記事です。

あらすじ

時はまさに大AWS時代、この荒波をレガシー技術を駆使して乗りこなす、この世の全てはそこから持ってきた!

AWS-Makefile

AWS-Makefileは GNU Makeを使ってAWS Cloudformationのスタックと AWS EC2のAMIをビルドする為の共通定義インクルードファイルとユーティリティスクリプト群を一式にまとめたものです。複数の CloudformationスタックとカスタムAMIを組み合わせたビルドを下記の様な Makefileを記述するだけで定義できます。

Makefile
include AWS-Makefile.mk

KEY_NAME=your-key-name
CW_NAME_TAG=customweb:latest

all: MyCustomWeb

MyCustomWeb: MyNetwork MyCustomWebAmi MyCustomWebStack

MyCustomWebStack: CW_AMI_ID = $(shell ./amibake id $(CW_NAME_TAG))
MyCustomWebStack: CC_OPTS = --parameters ParameterKey=NetworkStackName,ParameterValue=MyNetwork ParameterKey=MyCustomWebAmiId,ParameterValue=$(CW_AMI_ID) ParameterKey=KeyName,ParameterValue=$(KEY_NAME)
MyCustomWebStack: $(DAM)/MyCustomWeb.stack

MyCustomWebAmi: AB_NAME_TAG = $(CW_NAME_TAG)
MyCustomWebAmi: $(DAM)/MyCustomWeb.ami

MyNetwork: $(DAM)/MyNetwork.stack

clean:
    @./cfn_delete.sh --wait MyCustomWeb
    ./amibake rmi $(CW_NAME_TAG)
    @./cfn_delete.sh --wait MyNetwork
    @rm -f $(DAM)/*.stack $(DAM)/*.ami

この Makefileが makeコマンドを実行すると MyNetwork MyCustomWebAmi MyCustomWebStackターゲットが順にビルドされ、構築されたWebサーバにアクセスしてみると。。。はい、拍手!

MA!.png

最初にインクルードしている AWS-Makefile.mkは現状こんな感じです。まだ幾つか不便な箇所は有るので、適宜修正&追加していく予定です。内容を簡単に説明しておくと .amwmakeディレクトリに指定の *.stackファイルが無い場合は CFnTemplate/*.yamlを create-stackして作成、*.amiファイルが無い場合は AMIBakefile/*.amibを amibake buildして作成、既にビルド済みの場合は前回ビルドしたものを削除してからビルドする等の定義を行っています。

AWS-Makefile.mk
DAM=.awsmake
CFNT=CFnTemplate
AMIB=AMIBakefile
TOOL_PATH=./
CFN_CREATE=cfn_create.sh
CFN_DELETE=cfn_delete.sh
AMIBAKE=amibake

$(DAM)/%.stack: $(CFNT)/%.yaml
    @if [[ -f $@ ]]; then \
        echo $* stack clean; \
        $(TOOL_PATH)$(CFN_DELETE) --wait $* $(CD_OPTS); \
    fi
    @echo $* stack build
    @$(TOOL_PATH)$(CFN_CREATE) --wait $* $< $(CC_OPTS)
    @if [[ ! -d $(DAM) ]]; then \
        mkdir -p $(DAM); \
    fi
    @touch $@

$(DAM)/%.ami: $(AMIB)/%.amib
    @if [[ -f $@ ]]; then \
        echo $* ami clean; \
        $(TOOL_PATH)$(AMIBAKE) rmi $(AB_NAME_TAG) $(AB_OPTS); \
    fi
    @echo $* ami build
    @$(TOOL_PATH)$(AMIBAKE) build -f $< $(AB_OPTS)
    @$(TOOL_PATH)$(AMIBAKE) push $(AB_NAME_TAG) $(AB_OPTS)
    @if [[ ! -d $(DAM) ]]; then \
        mkdir -p $(DAM); \
    fi
    @touch $@

cfn_create & cfn_delete

AWS-Makefile.mkで使用している cfn_create.shと cfn_delete.shは AWS CLIの cloudformationコマンドをラップして、作成するスタック名とテンプレートファイル名、削除するスタック名だけ指定すればよい様にしたスクリプト、–waitオプションで作成、削除の完了待ちをする機能を追加してます。現状 –parametersオプションの指定が冗長なので、近いうちに何とかしたいなと

$ ./cfn_create.sh [--profile <prof>] [--wait] <stack_name> <template_file> [more parameters]

$ ./cfn_delete.sh [--profile <prof>] [--wait] <stack_name> [more parameters]

cfn_create.shに渡している Cloudformationテンプレートは特に解説するつもりは無かったのですが、テンプレート間で作成リソースIDを受け渡しを行っているクロススタック参照部分はわりと調べたので記述例として上げておきます。

CFnTemplate/MyNetwork.yaml(一部抜粋)

Outputs:
  MyVPCId:
    Value: !Ref MyVPC
    Export:
      Name: !Sub ${AWS::StackName}-MyVPCId

CFnTemplate/MyCustomWeb.yaml(一部抜粋)

Resources:
  MyCustomWebSg:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: MyCustomWebSg
      VpcId:
        Fn::ImportValue:
          !Sub ${NetworkStackName}-MyVPCId

Parameters:
  NetworkStackName:
    Type: String
    Default: MyNetwork

amibake

AWS-Makefile.mkで使用している amibakeは AWS CLIと packerコマンドをラップして、Dockerfile風の定義ファイルでカスタムAMIを作成したり、Dockerイメージ風の name:tag で AMIを管理出来る様にしたツールコマンドです。何でこんなものを作ったかというと Docker確かに便利で情報系のサービスや検証環境の構築には凄い良いのだけれど、ミッションクリティカルな用途にはインフラが多重化して障害点が増えるし、パフォチュー&トラシューのノウハウも必要やし、Dockerと同じ操作感でネイティブクラウドインフラが構築出来ればいいじゃね?という発想から、docker-compose相当の部分は Terraformと Cloudformationのどちらを採用するか一瞬検討したけど、管理情報を別建てで保持しなくていいのが楽そうだったので後者を採択

$ amibake tag ami-xxxxxxxx amazonlinux:201703
$ amibake id amazonlinux:201703
$ amibake search amazonlinux

$ # After writing AMIBakefile
$ amibake build
$ amibake push my_app:20170819
$ amibake run my_app:20170819
AMIBakefile/MyCustomWeb.amib
# AMIBakefile: MyCustomWeb
MAINTAINER nmrmsys
FROM ami-da9e2cbc # Amazon Linux AMI 2017.09.1 (HVM), SSD Volume Type
USER ec2-user
#BUILDER "vpc_id":"vpc-xxxxxxxx","subnet_id":"subnet-xxxxxxxx","associate_public_ip_address":true

COPY AMIBakefile/MyCustomWeb.sh ~/
COPY AMIBakefile/html/index.html ~/

RUN sudo sh ~/MyCustomWeb.sh
RUN sudo mv ~/index.html /var/www/html/

ami_backups & aws_stops

AWS-Makefileとは全く関係無いですが AWS CLIと jqの組み合わせで作ってみたシリーズのスクリプトで ami_backupsは EC2のタグにバックアップ指定を設定しておくと取ってくれるヤツ、売りはAMIでn世代バックアップとか出来る点と –dry-runオプションで動作確認出来る様にした所でしょうか、aws_stopsは RDSのSingleAZインスタンスが停止が可能になったけど 1週間で勝手に上がってくるぜって時に、なら毎日朝晩に開始停止をすればいいんじゃね? と RDS ついでに EC2も同じくタグ設定に基づいて所定の時刻に開始停止させるようにしたヤツです。

ami_backups [--profile <prof>] [--all | <inst>] [--dry-run] [--reboot] [--oneshot [suffix]] [--no-wait]

# Tag Setting Backup Mode
# Setting Backup Instance Tag 
# Backup:on, Generation:3, Reboot:off (optional) 
ami_backups --profile prof1 --all 

# Single Instance Backup Mode
ami_backups --profile prof1 inst1 

# First time running recommended to add --dry-run option.
aws_stops --profile <prof> [--dry-run <target_time>]

# Setting Stop/Start Instance Tag 
# Stop: 22:00, Start: 08:00 
aws_stops --profile prof1 

# First time running recommended to add --dry-run option.
# In actual operation, use cron

車輪の再発見とか

まーあれですよ『枯れた技術の水平思考』とでも言うと思ったかい、その態度、想定の範囲内だよ!
ルネサンスが古代ギリシャ・ローマの文化の再発見であったかのように歴史は何度でも繰り返される。
であるなら、最新のコジマ技術を獲得するのと同程度には、過去の用兵術にも目を向けるべきであろう

参考文献

続きを読む

Cloud9 on Fargate など模索の経過報告

こんなの書いていきます

  • 利用例
  • 小ネタ

利用例

この辺に使えないかと模索しています

  • ECS のあふれたタスク処理
  • 踏み台サーバ
  • Cloud9 リモートサーバ

ECS のあふれたタスク処理

aws ecs describe-clusters--include "STATISTICS" を付与すると返ってくる pendingEC2TasksCount が 1 以上であれば run-task--launch-type FARGATE を指定。
とはいえスケーリングの設定やアプリケーションの作り次第では必ずしもこの負荷の逃がし方が適切でもなく、目下思案中。

踏み台サーバ

多段 SSH するときだけ起動する EC2 はありませんか?
Fargate にしてしまいましょう。

これくらいあれば最低限動きそうです。
https://github.com/pottava/fargate-shell/tree/master/serverless-bastion/docker

  • sshd に -d をつけて起動 することで、セッションが切れたら Fargate も落ちる
  • パスワード、または公開鍵認証(鍵は S3 を経由して配布)
  • sudo させるかどうかを環境変数 ENABLE_SUDO で制御

Docker イメージとして踏み台を管理できれば、これをベースに作業内容をログに残したり、渡す IAM タスクロールで AWS-CLI の利用できるコマンドを制限したりもある程度自由にカスタマイズできそうです。

Cloud9 リモートサーバ

Fargate で Cloud9 のリモートサーバを管理すれば、Docker イメージで作業者の環境を管理できそうです。ベースイメージはこんな感じ。
https://github.com/pottava/fargate-shell/blob/master/serverless-cloud9/docker/Dockerfile

  • AmazonLinux に必要なミドルウェアを入れたもの
  • Cloud9 からの SSH 接続に必要な鍵は S3 を経由して連携

その上で、開発に必要なミドルウェアを載せて、Cloud9 のリモートサーバに指定すれば IDE が起動します。例えば go v1.9.2 ならこんな感じ。
https://github.com/pottava/fargate-shell/tree/master/samples/cloud9-go1.9

ただし・・

  • docker in docker できないため、Cloud9 の
    IDE に c9.ide.lambda.docker はインストールできない
  • 作業が終了したら Fargate を明示的に停止する必要がある

のが惜しい感じになりました。AWS さんのネイティブ対応が待たれます。それにしても Fargate、ハンズオンや Jupyter notebook を配るといったことにも応用できそうです。

小ネタ

(以下 2017/12/12 時点のものであり、また仕様やドキュメントとして記載がないものも取り上げているため、機能追加や特にアナウンスなく変更が入る可能性も十分にありえます)

渡ってくる環境変数

  • AWS_DEFAULT_REGION
  • AWS_REGION
  • AWS_CONTAINER_CREDENTIALS_RELATIVE_URI

最後の変数でタスクに割り当てた IAM ロールを確認できる ものの
コンテナ内であればそのロールは Assume された状態なので
例えば以下のコマンドも同等の情報を返してくれます。普通。
$ aws sts get-caller-identity

渡せない環境変数

EC2 ホストがないので ECS Agent にオプションが渡せない。例えば ECS_ENABLE_CONTAINER_METADATAtrue にしたいけどできない。ECS_CONTAINER_METADATA_FILE が渡ってくると地味に便利なんですが・・

タスク定義の制約

とある理由で docker in docker がしたかったのですが、おそらくセキュリティ上の理由から ECS では設定できる以下の項目が使えません。まあ、はい。

  • linuxParameters/capabilities
  • privileged

awsvpc の制約

Fargate の注意点というわけではないものの、Fargate である以上 awsvpc が避けられないので。
ECS ではコンテナの定義として containerPorthostPort を別にすることができましたが、awsvpc ではそれが許されません。異なったポートを定義すると register-task-definition で弾かれます。

An error occurred (ClientException) when calling the RegisterTaskDefinition operation: When networkMode=awsvpc, the host ports and container ports in port mappings must match.

ENI の上限

起動するコンテナごとに一つ消費されていくので、初期リージョン上限である 350 個が一つのハードルでしょうか。Lambda ほどスケールしてくれませんが、まあ、ユースケース違うしね。ところでいつか awsvpc でない起動方法は追加されるんでしょうか。されない気もしますね・・

続きを読む

spfレコードに対象hostのIPアドレスを追加しまくったら長いと怒られた

前提

  • DNSはroute53
  • サーバはクラウド
    • IPアドレスセットがバラバラになる
  • メールは各サーバから
  • システムが構築されるたびにwebサーバ群のIPを登録
    • +ip4:123.456.789.0/24 こんな感じ

増やしすぎて怒られる

512バイトが上限なのでまだいけると思ってたけど怒られる、、、
kako-wEIvFfYWAh0XzByQ.png

サポートされる DNS リソースレコードタイプ – Amazon Route 53 – http://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/ResourceRecordTypes.html

TXT レコードには、スペースで区切られた、二重引用符で囲まれた文字列のリストが含まれます。1 つの文字列には、最大 255 文字が含まれます

RFCで連結した後の1つのレコードが450バイト以下であることが目安とされているため
妥当と思われる255バイトで制限かかってるところが多いらしい

 

分割をしよう

間違いから学ぶSPFレコードの正しい書き方 : 迷惑メール対策委員会 – http://salt.iajapan.org/wpmu/anti_spam/admin/operation/information/spf_i01/

TXTレコードを単純に分けてはダメらしい

baseレコードとincludeレコードに分ける

オリジナル

"v=spf1 mx ip4:123.456.000.0/24 +ip4:123.456.111.0/24 +ip4:123.456.222.0/24 +ip4:123.456.333.0/24 +ip4:123.456.444.0/24 +ip4:123.456.555.0/24 +ip4:123.456.666.0/24 +ip4:123.456.999.0/24 +ip4:123.456.888.0/24 +ip4:123.456.999.0/24 include:spf-bma.mpme.jp ~all"

分割

baseレコード:mydomain.com. IN TXT

"v=spf1 mx ip4:123.456.000111.0/25  include:spf.mydomain.com include:spf-bma.mpme.jp ~all"

includeレコード:spf.mydomain.com IN TXT

"v=spf1 +ip4:123.456.111.0/24 +ip4:123.456.222.0/24 +ip4:123.456.333.0/24 +ip4:123.456.444.0/24 +ip4:123.456.555.0/24 +ip4:123.456.666.0/24 +ip4:123.456.999.0/24 +ip4:123.456.888.0/24 +ip4:123.456.999.0/24 ~all"

レコードチェックする

無事通りましたヽ(・∀・)人(・∀・)ノ
http://www.kitterman.com/spf/validate.html
実際のメールでも確認
 

問題点

includeレコードの中身が増え続けていくのですぐに同じ問題にぶち当たる可能性

解決策案

  • スーパー楽な手段

    • 第2オクテットで登録 : 123.456.0.0/25

      • 漏れが発生する可能性は残る
  • さらに入れ子にする
    • ベース – include:ドメインtxt include:txt ~all”
    • ベース – include:ネットワークセット include:ネットワークセット ~all”
      • ネットワークセット – include:ドメイン名txt ~all”
    • ドメイン名別にtxtを用意すると削除がしやすい
  • MXをアウトソースにする
    • 余計な作業が減る

      • お金はかかる

続きを読む