20190228のAWSに関する記事は16件です。

AWSのIAMロールとポリシーの違い

■概要

・AWSのIAMとは?(Identity and Access Management)

 AWSのリソースに紐付けることができる権限設定を行うサービスの事。
 (AWSのリソースは、EC2,RDS,Lambda...etcといった全てのサービスで生成するものに必要な物となります)

・IAMの4つの要素

 IAMには「グループ」、「ユーザ」、「ロール」、「ポリシー」の4つの要素がある。
 (グループとユーザについては別途機会があれば^^;)

■結論

IAMロール(Role) = 「AWSのリソースに付与するもの」で、実態はIAMポリシーをグルーピングしたもの
IAMポリシー(policy) = 「AWSリソースにアクセスするための権限設定」で、AWSが最初から用意してくれている

つまりポリシーはロールに内包されている物であり、ロールはAWSリソースに付与されるものである。

例えば、あるEC2には一つだけIAMロールが付与出来るが、そのロールに様々なポリシーを付与する事で、そのEC2は様々なAWSリソースにアクセスする事が出来るようになる。
(AWSCliコマンドから、S3バケットにアクセスしたり、Lambdaを実行したり出来る。)

IAM.PNG

■詳細

・IAMロールの細かい話

 - IAMロールは作成する際に、どのサービスで使うかを選択する必要がある。
 (よく使われるサービスがEC2とLambdaなので、下記のキャプチャのように、上のほうにピックアップされている)
IAM2.PNG

・IAMポリシーの細かい話

 - IAMポリシーは、AWSが用意してくれているもの意外に、自作することも出来る(インラインポリシー)
 (その際は、ビジュアルエディタでポチポチサービスを選んで作ることも、JSONを書いて作ることも出来る)
  →これは、AWSが用意しているポリシーでは、細かい設定が仕切れない事や、対象リソースを絞ったポリシーを作る必要がある人の為。
  →さらにロールに設定できるAWSのマネージメントポリシーは最大10個までしか付与できないため(インラインポリシーなら無限?に付与できる)

■最後に

 今回はAWSの最初によく躓く最初にIAM周りにふれてみました。
 IAMグループは、さらにロールをグルーピング化した物で、IAMユーザはAWSにログインした時orプログラムに権限を渡すときに作るものです。
 ↑説明が雑なので、要望があれば別途書きます。
 以上、フィードバックやご指摘等いただけると大変ありがたいです!!よろしくお願いいたします!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[AWS]SAMを使ってLambdaを定期実行するHelloWorld

LambdaファンクションをCloudWatch Eventを使って定期実行してみたいと思います。

0. 環境

$ docker -v
Docker version 18.09.2, build 6247962
$ python --version
Python 3.7.0
$ pip --version
pip 19.0.3
$ aws --version
aws-cli/1.16.114 Python/3.7.0 Darwin/18.2.0 botocore/1.12.104
$ sam --version
SAM CLI, version 0.11.0

AWSCLIの設定(aws configure)がしてある前提で進めます。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-configure.html#cli-quick-configuration

1. HelloWorld実行(開発準備)

1-1. テンプレ作成

$ sam init -r python3.7 -n my-app

今回はpython3.7で開発します。
my-appというプロジェクト名で作成します。

以下の通り、作成されます。

$ cd my-app
$ tree
.
|-- README.md
|-- event.json
|-- hello_world
|   |-- __init__.py
|   |-- __pycache__
|   |   |-- __init__.cpython-37.pyc
|   |   `-- app.cpython-37.pyc
|   |-- app.py
|   `-- requirements.txt
|-- template.yaml
`-- tests
    `-- unit
        |-- __init__.py
        |-- __pycache__
        |   |-- __init__.cpython-37.pyc
        |   `-- test_handler.cpython-37.pyc
        `-- test_handler.py

5 directories, 12 files

1-2. 必要ライブラリインストール

$ cd hello_world/
$ pip install -r requirements.txt -t build/
$ cp *.py build/
$ cd ..

templete.yamlの以下の部分を書き換えてください

            CodeUri: hello_world/

            CodeUri: hello_world/build/

1-3. Lambdaテスト実行

作成したeventファイルで実行します。
hello worldが出力されればOK
初回起動はDockerが立ち上がるのに時間がかかります。

$ sam local invoke -e event.json
〜(略)〜
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}

1-4. Lambdaローカルエンドポイント作成

以下コマンドでローカルにLambdaのエンドポイントが作成できます。

$ sam local start-lambda

実行はCLIかSDKを使って行えます。
今回はCLIを使って動作確認しました。

$ aws lambda invoke --function-name "HelloWorldFunction" --endpoint-url "http://127.0.0.1:3001" out.txt
{
    "StatusCode": 200
}
$ cat out.txt 
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}

これで開発環境は整いました。

2. Lambdaデプロイ

2.1 template.yaml作成

https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
    HelloWorldFunction:
        Type: AWS::Serverless::Function
        Properties:
            CodeUri: hello_world/build
            Handler: app.lambda_handler
            Runtime: python3.6
            Events:
                HelloWorld:
                    Type: Schedule
                    Properties:
                        Schedule: cron(30 23 ? * MON-FRI *)

2-2. パッケージ化

S3バケットがない場合は作ります

$ aws s3 mb s3://my-app-lambda
make_bucket: my-app-lambda

以下コマンドでパッケージ化したものをS3に設置

$ sam package --template-file template.yaml --output-template-file packaged.yaml --s3-bucket my-app-lambda
Uploading to 614e5f60f81b1272edd607033afa55d8  965373 / 965373.0  (100.00%)
Successfully packaged artifacts and wrote output template to file packaged.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file <PATH>/packaged.yaml --stack-name <YOUR STACK NAME>

2-3. デプロイ

$ sam deploy --template-file packaged.yaml --stack-name my-app --capabilities CAPABILITY_IAM

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - my-app

AWSコンソールから,CloudFormation, Lambda, CloudWatchイベント を確認してみてください。
作成されているはずです。

app.pyに

削除する際は、CloudFormationから削除すれば関連するサービスまとめて削除してくれます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[AWS]SAMを使ったLambdaを定期実行するHelloWorld

LambdaファンクションをCloudWatch Eventを使って定期実行してみたいと思います。

0. 環境

$ docker -v
Docker version 18.09.2, build 6247962
$ python --version
Python 3.7.0 (default, Feb 28 2019, 22:25:31)
[Clang 9.0.0 (clang-900.0.39.2)]
$ pip --version
pip 19.0.3 from /Users/Hiroki/.pyenv/versions/3.7.0/lib/python3.7/site-packages/pip (python 3.7)
$ aws --version
aws-cli/1.16.114 Python/3.7.0 Darwin/18.2.0 botocore/1.12.104
$ sam --version
SAM CLI, version 0.11.0

AWSCLIの設定(aws configure)がしてある前提で進めます。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-configure.html#cli-quick-configuration

1. HelloWorld実行(開発準備)

1-1. テンプレ作成

$ sam init -r python3.7 -n my-app

今回はpython3.7で開発します。
my-appというプロジェクト名で作成します。

以下の通り、作成されます。

$ cd my-app
$ tree
.
|-- README.md
|-- event.json
|-- hello_world
|   |-- __init__.py
|   |-- __pycache__
|   |   |-- __init__.cpython-37.pyc
|   |   `-- app.cpython-37.pyc
|   |-- app.py
|   `-- requirements.txt
|-- template.yaml
`-- tests
    `-- unit
        |-- __init__.py
        |-- __pycache__
        |   |-- __init__.cpython-37.pyc
        |   `-- test_handler.cpython-37.pyc
        `-- test_handler.py

5 directories, 12 files

1-2. 必要ライブラリインストール

$ cd hello_world/
$ pip install -r requirements.txt -t build/
$ cp *.py build/
$ cd ..

templete.yamlの以下の部分を書き換えてください

            CodeUri: hello_world/

            CodeUri: hello_world/build/

1-3. Lambdaテスト実行

作成したeventファイルで実行します。
hello worldが出力されればOK
初回起動はDockerが立ち上がるのに時間がかかります。

$ sam local invoke -e event.json
〜(略)〜
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}

1-4. Lambdaローカルエンドポイント作成

以下コマンドでローカルにLambdaのエンドポイントが作成できます。

$ sam local start-lambda

実行はCLIかSDKを使って行えます。
今回はCLIを使って動作確認しました。

$ aws lambda invoke --function-name "HelloWorldFunction" --endpoint-url "http://127.0.0.1:3001" out.txt
{
    "StatusCode": 200
}
$ cat out.txt 
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}

これで開発環境は整いました。

2. Lambdaデプロイ

2.1 template.yaml作成

https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
    HelloWorldFunction:
        Type: AWS::Serverless::Function
        Properties:
            CodeUri: hello_world/build
            Handler: app.lambda_handler
            Runtime: python3.6
            Events:
                HelloWorld:
                    Type: Schedule
                    Properties:
                        Schedule: cron(30 23 ? * MON-FRI *)

2-2. パッケージ化

S3バケットがない場合は作ります

$ aws s3 mb s3://my-app-lambda
make_bucket: my-app-lambda

以下コマンドでパッケージ化したものをS3に設置

$ sam package --template-file template.yaml --output-template-file packaged.yaml --s3-bucket my-app-lambda
Uploading to 614e5f60f81b1272edd607033afa55d8  965373 / 965373.0  (100.00%)
Successfully packaged artifacts and wrote output template to file packaged.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file <PATH>/packaged.yaml --stack-name <YOUR STACK NAME>

2-3. デプロイ

$ sam deploy --template-file packaged.yaml --stack-name my-app --capabilities CAPABILITY_IAM

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - my-app

AWSコンソールから,CloudFormation, Lambda, CloudWatchイベント を確認してみてください。
作成されているはずです。

削除する際は、CloudFormationから削除すれば関連するサービスまとめて削除してくれます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オリジナルKdB(科目検索)を作ってみよう -番外1- FaaS下準備編

これまでの記事

スクリーンショット 2019-02-22 16.11.47.png

この記事を読む前に

ある特定の人にしかわからない単語が出現する可能性が高いです。あらかじめご了承ください。

この記事での開発環境

  • MacOS 10.14.3
  • Visual Studio Code 1.31.1
  • Node.js LTS 10.15.1

はじめに

「オリジナルKdB(科目検索)を作ってみよう」の番外編です。これまではExpress.jsは使わず、サーバレスなシステムを作っていきましょう。シリーズでやっていたようにExpress.jsでエンヤコラはやらずに、関数を作るだけで簡単にサービスを作ることができます。

FaaS について

IaaS,BaaS,PaaS,SaaSなどに連なるサービスの1つがFaaS(Function as a Service)です。関数のみを記述するだけで良く、サーバ周りの記述を省略できる画期的なサービスです。

「サーバ周りに関するコードを書かない → サーバを用意する必要がない」ことからサーバレスアーキテクチャと呼ばれます(縮めて「サーバレス」と呼ばれることが多いです)。

  • lambda関数を用意する。
  • FaaSにアップロードする。

たった2ステップで簡単です。しかも、サーバを持たないのでDDoS攻撃によってサーバが落ちることがありません。データベースに直接接続していないため「サーバに侵入されて個人情報が...!?」というケースもありません。しかもサーバを24時間365日稼働しているわけではありません。リクエストが来た時だけ稼動するのでコストを抑えることができます。

簡単・安全・安いがFaaSのウリです。

FaaSを提供しているサービス

FaaSを提供しているサービスはいくつかあります。

AWS lambda

スクリーンショット 2019-02-28 22.13.48.png

AWS(Amazon Web Service)で提供されているFaaSです。
さすがAWSというだけあって、毎月100万リクエスト、400,000 GB-秒まで無料とめちゃめちゃ太っ腹です。Node.js以外にPython, C#(.NET Core), Go, Java, Rubyと多様な言語に対応しています。まず始めるならオススメしたいFaaSと言えます。

また、AWSが提供する他のサービスとの連携に優れており「S3(AWSの提供するクラウドストレージ)にファイルをアップロードしたら自動的に圧縮する」ような処理を簡単に組み立てることができます。

Google Cloud Functions

スクリーンショット 2019-02-28 22.27.18.png

GCP(Google Cloud Platform)で提供されているFaaSです。
こちらも200万リクエスト、計算時間100万秒まで無料と太っ腹。こちらはNode.js限定(Go, Pythonがベータ版)です。専用のライブラリを入れる必要がありますがlambda関数の書き方が簡潔であり、日本語のレファレンスが充実しています。

GCPの他のサービスと連携しやすいのも利点です。GCPのサービスは欲しいところに欲しい説明をくれるので、初心者から上級者まで使いこなせるのがいいですね。

IBM Cloud Functions

スクリーンショット 2019-02-28 22.40.37.png

IBM Cloud で提供されているFaaSです。
使ったことがないので料金体系がわかりませんが、見た感じとても安いです。何よりもNode10.XSwift4.X, PHP 7.X, Python3.Xと、新しいバージョンに対応しているのが素晴らしいです。実行環境で最新の機能が使えるのが最高です。

Zeit Now

スクリーンショット 2019-02-28 22.48.24.png

爆速デプロイをウリにしているFaaSです。料金体系がわかりやすく、個人サイト程度の規模なら無料で使うことができます。課金をすれば激安定額なのに商業利用に耐えるスペックで利用できます。また、lambda関数をアップロード時に本番環境を想定したサーバに一旦保存されるので「本番にデプロイしたら不具合が起きた!!!」なんてことを事前に検証することができます。

とりあえず完全無料でFaaSを初めてみたいという方にはオススメです。

上記の他にもいくつもFaaSを提供しているサイトがあります。自分好みなサービスを見つけてがっつり使っていきましょう??

ところで今回すること → 下準備

Lambda系のサービスに共通する特徴にファイル入出力周りが安定しないことが挙げられます。そのため、これまで使っていたfsモジュールでのファイル読み込みは使わないように調整しましょう。

参考記事 → AWS Lambdaでファイル入出力をしてみる

module.exportsしたファイルはちゃんと読み込んでくれるので、CSVファイルに書かれている全ての情報をjavasctiptファイルに書き出しましょう。

まずは必要なパッケージをインストールします。

$ npm install iconv-lite csv 

そしたら、以下のようにdump.jsを作ります。

dump.js
const fs = require('fs');
const iconv = require('iconv-lite')
const parse = require('csv').parse;

// CSVファイル(Shift_jis)を読み込んでパース
const dataGet = () => {
    const text = fs.readFileSync("kdb.csv")
    const ret = iconv.decode( Buffer.from(text), "Shift_JIS");
    const dataArray = []
    parse(ret, (err, data) => {
        if (err) console.log(err.message)
        else {
            data.forEach( (element) => {
                dataArray.push(element)
            });
        }
    });
    return dataArray
}

// 配列をもらってJSON化
const dataToJson = async (dataArray) => {
    const dataJson = { subjects: [] }
    dataArray.forEach( (element) => {
        dataJson.subjects.push( 
            {
                id: element[0],
                name: element[1],
                lessonType: element[2],
                credit: element[3],
                level: element[4],
                semester: element[5],
                period: element[6],
                place: element[7],
                teachers: element[8],
                summary: element[9],
                remarks: element[10],
                otherCourseStatus: element[11],
                otherCourseInfo: element[12],
                engName: element[13],
                engId: element[14],
                searchOption: element[15],
                timestamp: element[16],
            }
        )
    })
    return dataJson
}

// 即時関数でdata.jsを生成
(async () => {
    const data = await dataGet()
    const json = await dataToJson(data)
    const myJs = `const json = ${JSON.stringify(json)};
module.exports = json;`
    fs.writeFileSync("data.js", myJs)
})()

※async/awaitについて説明してると記事が爆発的に大きくなってしまうので割愛します。
async/awaitについての参考サイト(Promiseと一緒に覚えると楽です)

実行して、新しくdata.jsが作られていることを確認しましょう。

$ node dump.js
$ ls
dump.js               kdb.csv         package.json
package-lock.json     node_modules    data.js

data.jsの中身をみればわかりますが、kdb.csvの情報が全て書き出されていることがわかります。もしうまく書き出せていなかったら、ごめんなさい...コメントにお願いします。

とりあえず今回はこの辺で

下準備だけで内容のない記事となってしまいましたが、次回からがっつりFaaSを使っていきますので許してください。
ちなみに、AWSとGCPの2つを触る予定です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Session Managerを使用して30分でSSHログインをやめることができた話 〜JAWS DAYS 2019〜

はじめに

JAWS DAYS 2019に初めて参加してきました!!

iOS の画像.jpg

すごい盛り上がりで、約2000人が参加されたそうです!キャンセル待ちも多数いたとか!!!
朝から参加しましたけど、内容が濃すぎて後半は頭がパンクしておりました…
自分が参加したセッションの中でも特に多くの聴講者がいたと感じたのは、EKS、CI/CDでした。
EKSは皆さん注目しているんでしょうね!弊社も本番環境docker化計画で密かに検討中です!

JAWS DAYS 2019とは?
JAWS DAYS 2019 資料まとめ

タイトルと注目のサービスが違うじゃん…

そうなんです。ですが、今回はタイトルにある通りAWS Session Managerについてお話しようと思っております。
JAWS DAYS 2019でもAWS Session Managerのセッションがありました。
弊社の環境では、踏み台サーバを経由してEC2インスタンスにSSHログインする、という流れになっていたのですが、セッションのタイトルに 1日でSSHをやめることができた話 と書いてあるのを見つけ、すぐに 僕が作った最強のタイムテーブル に追加しました。

セッションの資料:1日でSSHをやめることができた話 ~AWS Systems Manager Session Manager 導入と運用Tips~

セッションを聞いてみて

  • SSHログインが不要
    • 踏み台サーバが不要で余計なEC2インスタンスが減らせる!
    • 公開鍵・秘密鍵の管理が不要!
    • SSHのインバウンドポートが不要
  • IAMで一元管理できる
    • 人が増えてもサクッと追加できる!
  • コンソールでの実行履歴が残せる
    • S3 or CloudWatchLogsに送れる!
    • CloudWtachLogs→lambdaとかでSlackに通知もできる!

マジかよ…メリットしかないやんけ…

というわけで、すぐにStaging環境で試してみました!

試した結果

1日というか、 30分 でシェルアクセスが出来ました!!!
登壇者の方も「1日と言っていますが、実際かかったのは3時間くらいです。」と仰っていましたが、それ以上の簡単さでした!

というわけで、自分が設定した手順を説明します。

手順

  1. EC2に割り当てられているロールに AmazonEC2RoleforSSM をアタッチする

以上でした。

スクリーンショット 2019-03-01 15.39.22.png

え…?マジで…?

実際に最小限これで出来ました。本当はよりセキュアにする設定もあるのですが、とりあえずお試しなのでよしとします。

本来の手順であれば、

  1. EC2にSSMエージェントをインストール
  2. IAMインスタンスプロファイルロールの作成
  3. EC2に上記のロールを割り当てる
  4. その他、よりセキュアな設定

とするのですが、弊社の環境はElasticBeanstalkを使用して、オートスケールするようにしています。
EC2にはすでに専用のロールが設定されており、そこに AmazonEC2RoleforSSM をアタッチしました。
また、SSMエージェントは2017年9月以降のAmazon Linux AMI に標準インストールされています。

そのため、たった1ステップでインスタンスにシェルアクセスができるようになったというわけです!
(セキュアな設定…?Staging環境だから大丈夫!!)

それだけで30分はかかりすぎでしょ…

ポリシーをアタッチしてもすぐに反映されてなくて…色々調べてから戻って来たら表示されてて…

今後の予定

Staging環境でしばらく使ってみて、問題がなければセキュアな設定をしたのち、本番環境にも適用しようと思います!

おわりに

JAWS DAYS 2019に行けてとてもよかったです!!
運営の皆さま、登壇者の皆さま、スポンサーの皆さま、本当にありがとうございました!!
この流れで頑張ってEKSを使って本番環境docker化していきたいと思います!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Cloud9の標準Dockerイメージからコンテナを起動しようとするとExited (1)となる

AWS Cloud9で新規作成した環境に標準で用意されているDockerイメージを使ってみようとしたが上手くいかず調査したときのメモ。

AWS Cloud9の環境にはDockerが既定でインストールされており、下記の4つのイメージが標準で作成されている。
これでコンテナを作って使えないかと考えた。

ec2-user:~/environment $ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
lambci/lambda       python2.7           b92d1404520d        3 weeks ago         951MB
lambci/lambda       nodejs6.10          6d5686f02fe2        3 weeks ago         996MB
lambci/lambda       nodejs4.3           ce68cbe2ecf8        3 weeks ago         945MB
lambci/lambda       python3.6           659e6b066789        3 weeks ago         1.08GB

しかし、runコマンドでイメージからコンテナを起動しようとすると、立ち上がってすぐにExited (1)となりコンテナが落ちてしまう。

ec2-user:~/environment $ docker run -itd b92d1404520d
7cc7c4396751e98e3f6ec1761041d0ed3f978fe5b7a5db57cb6f70d59b825292
ec2-user:~/environment $ docker run -itd 6d5686f02fe2
86e6cb64fa4a6af7640bb3158f0cbb3cf163bc40188e7f1ff3c717265113e50a
ec2-user:~/environment $ docker run -itd ce68cbe2ecf8
f9ea26de0f5edd86a0e7c413b979a34dd7ae69c5346f92c694bd1d1f29501fc0
ec2-user:~/environment $ docker run -itd 659e6b066789
a32d9cf3042cbdf0a627fc445e3c39db7ea443ff624f5fd9d405f0edeab91449
ec2-user:~/environment $ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
a32d9cf3042c        659e6b066789        "/var/lang/bin/pytho…"   10 seconds ago       Exited (1) 3 minutes ago                       keen_galileo
f9ea26de0f5e        ce68cbe2ecf8        "/usr/local/lib64/no…"   10 seconds ago       Exited (1) 3 minutes ago                       reverent_tereshkova
86e6cb64fa4a        6d5686f02fe2        "/var/lang/bin/node …"   10 seconds ago       Exited (1) 3 minutes ago                       vigorous_swanson
7cc7c4396751        b92d1404520d        "/usr/bin/python2.7 …"   10 seconds ago       Exited (1) 3 minutes ago                       infallible_ardinghelli

--no-truncオプションでコンテナ起動時に実行されるCOMMANDを全文表示してみると、どうやらAWS Lambdaが関数を実行するときのコマンド実行シーケンスのように見える。(http://marcy.hatenablog.com/entry/2016/12/14/115953)

ec2-user:~/environment $ docker ps -a --no-trunc
CONTAINER ID     IMAGE               COMMAND                                                                                                                                                                          CREATED             STATUS                     PORTS               NAMES
a32d9cf3042c(略)   659e6b066789        "/var/lang/bin/python3.6 /var/runtime/awslambda/bootstrap.py"                                                                                                                    7 minutes ago       Exited (1) 7 minutes ago                       keen_galileo
f9ea26de0f5e(略)   ce68cbe2ecf8        "/usr/local/lib64/node-v4.3.x/bin/node --expose-gc --max-executable-size=160 --max-semi-space-size=150 --max-old-space-size=2547 /var/runtime/node_modules/awslambda/index.js"   7 minutes ago       Exited (1) 7 minutes ago                       reverent_tereshkova
86e6cb64fa4a(略)   6d5686f02fe2        "/var/lang/bin/node --expose-gc --max-executable-size=160 --max-semi-space-size=150 --max-old-space-size=2547 /var/runtime/node_modules/awslambda/index.js"                      7 minutes ago       Exited (1) 7 minutes ago                       vigorous_swanson
7cc7c4396751(略)   b92d1404520d        "/usr/bin/python2.7 /var/runtime/awslambda/bootstrap.py"                                                                                                                         8 minutes ago       Exited (1) 8 minutes ago                       infallible_ardinghelli

docker logsでログを見てみる。
これは明らかにAWS Lambda関数の実行に失敗したときのエラー。

ec2-user:~/environment $ docker logs a32d9cf3042c
START RequestId: 010a633a-xxxx-xxxx-xxxx-xxxxxxxxxxxx Version: $LATEST
Unable to import module 'lambda_function': No module named 'lambda_function'
END RequestId: 010a633a-xxxx-xxxx-xxxx-xxxxxxxxxxxx
REPORT RequestId: 010a633a-xxxx-xxxx-xxxx-xxxxxxxxxxxx Duration: 1 ms Billed Duration: 100 ms Memory Size: 1536 MB Max Memory Used: 19 MB

{"errorMessage": "Unable to import module 'lambda_function'"}
ec2-user:~/environment $ docker logs f9ea26de0f5e
START RequestId: 6da74752-xxxx-xxxx-xxxx-xxxxxxxxxxxx Version: $LATEST
Unable to import module 'index': Error
    at Function.Module._resolveFilename (module.js:325:15)
    at Function.Module._load (module.js:276:25)
    at Module.require (module.js:353:17)
    at require (internal/module.js:12:17)
END RequestId: 6da74752-xxxx-xxxx-xxxx-xxxxxxxxxxxx
REPORT RequestId: 6da74752-xxxx-xxxx-xxxx-xxxxxxxxxxxx  Duration: 9.35 ms       Billed Duration: 100 ms Memory Size: 1536 MB    Max Memory Used: 22 MB

{"errorMessage":"Cannot find module '/var/task/index'","errorType":"Error","stackTrace":["Function.Module._load (module.js:276:25)","Module.require (module.js:353:17)","require (internal/module.js:12:17)"]}
ec2-user:~/environment $ docker logs 86e6cb64fa4a
START RequestId: 64047b49-xxxx-xxxx-xxxx-xxxxxxxxxxxx Version: $LATEST
Unable to import module 'index': Error
    at Function.Module._resolveFilename (module.js:469:15)
    at Function.Module._load (module.js:417:25)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
END RequestId: 64047b49-xxxx-xxxx-xxxx-xxxxxxxxxxxx
REPORT RequestId: 64047b49-xxxx-xxxx-xxxx-xxxxxxxxxxxx  Duration: 9.66 ms       Billed Duration: 100 ms Memory Size: 1536 MB    Max Memory Used: 27 MB

{"errorMessage":"Cannot find module '/var/task/index'","errorType":"Error","stackTrace":["Function.Module._load (module.js:417:25)","Module.require (module.js:497:17)","require (internal/module.js:20:19)"]}
ec2-user:~/environment $ docker logs 7cc7c4396751
START RequestId: cc72da4b-xxxx-xxxx-xxxx-xxxxxxxxxxxx Version: $LATEST
Unable to import module 'lambda_function': No module named lambda_function
END RequestId: cc72da4b-xxxx-xxxx-xxxx-xxxxxxxxxxxx
REPORT RequestId: cc72da4b-xxxx-xxxx-xxxx-xxxxxxxxxxxx Duration: 0 ms Billed Duration: 100 ms Memory Size: 1536 MB Max Memory Used: 14 MB

{"errorMessage": "Unable to import module 'lambda_function'"}

どうやら私が使おうとしていたDockerイメージは、AWS Cloud9の本来の機能としてAWS Lambda関数の作成、テスト、デプロイを行う際に使用されるイメージで、正規の使い方ではないことをしようとしていたためエラーで落ちてしまっていた、ということでした。

https://docs.aws.amazon.com/ja_jp/cloud9/latest/user-guide/tutorial-lambda.html

結論として、Cloud9上でDockerを利用したい場合は、外部からpullするか、いちからbuildしましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

なる早でslackのスラッシュコマンドを作ってみる

はじめに

Serverless Framework使えば割と簡単にスラッシュコマンド作れるのでは?と思いどれくらいで作成できるのか試してみました。
あんまりいいアイディアが出てこなかったので三角関数の値を返すスラッシュコマンドを作ろうと思います。

/tri-func sin 30
0.5
/tri-func cos 60
0.5
/tri-func tan 45
1.0

こんな感じで(sinhとかconhがないのは勘弁)

APIを作る

プロジェクトを作成

$ mkdir <project> && cd <project>
$ sls create -t aws-python3 -n tri-func  
$ tree
.
├── handler.py
└── serverless.yml

pipenvの導入

標準のmathライブラリを使用してもいいのですが最近serverless-python-requirementsdockerizePipという依存関係があるライブラリをAmazonLinuxのDockerImageからビルドしてくれるものがあると知ったので、今回はnumpyを使用して三角関数の値を返します。
今回はpipenvserverless-python-requirementsを使用してライブラリを使用します。

pipenvで仮想環境を作りnumpyをインストールする

$ pipenv install --python 3.6.5
$ pipenv install numpy

serverless-python-requirementsのインストール

$ sls plugin install -n serverless-python-requirements

serverless.ymlを編集

profileを指定しない場合はdefaultが使用されます

serverless.yml
service: tri-func

provider:
  name: aws
  runtime: python3.6
  stage: stg
  region: ap-northeast-1
  profile: <hogehoge> #profileが複数あるので指定する

functions:
  get:
    handler: handler.get_tri
    events: #APIGatewayの設定
      - http: GET /

plugins:
  - serverless-python-requirements

custom:
  pythonRequirements:
    usePipenv: true # pipenvを使用する
    dockerizePip: non-linux # amazonlinuxでビルドする

handler.pyを編集

handler.py
import json
import numpy as np


def get_tri(event, context):
    # event['multiValueQueryStringParameters']['text']にコマンドの引数が入ってくる
    text = event['multiValueQueryStringParameters']['text'][0]
    text = text.split()

    try:
        tri = text[0]
        theta = int(text[1])
        if tri == 'sin':
            result = np.sin(np.pi*theta/180)
        elif tri == 'cos':
            result = np.cos(np.pi*theta/180)
        elif tri == 'tan':
            result = np.tan(np.pi*theta/180)
        else :
            result = 'エラー'
    except Exception as e:
        result = 'エラー'

    body = {
        'text': '結果: {}'.format(result),
    }

    response = {
        'statusCode': 200,
        'isBase64Encoded': False,
        'headers': {'Content-Type': 'application/json'},
        'body': json.dumps(body)
    }


    return response

デプロイ

$ sls deploy -v

Serverless: Stack update finished...
Service Information
service: tri-func
stage: stg
region: ap-northeast-1
stack: tri-func-stg
resources: 9
api keys:
  None
endpoints: # URLをメモっておく
  GET - https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/stg
functions:
  get: tri-func-stg-get
layers:
  None

デプロイを行うとエンドポイントが表示されるのでメモしておく

Slackでスラッシュコマンドを登録する

Slack > 管理 > カスタムインテグレーション > Slash > 設定を追加 からCommands からスラッシュコマンドを作成する

デプロイした際に表示されたエンドポイントをURLにペーストする
スクリーンショット 2019-02-28 17.00.33.png
そして作成!

スラッシュコマンドを使ってみる

とりあえずsin30, cos60, tan45でテスト
スクリーンショット 2019-02-28 17.02.35.png
スクリーンショット 2019-02-28 17.04.36.png
スクリーンショット 2019-02-28 17.05.15.png

成功ですね(桁は許して)

結局どれくらいかかった?

Serverless Frameworkの環境が整っていればおそらく10分程度でスラッシュコマンドが作れるのではないでしょうか。
ちなみにこの記事を書きながら作成していたので私は約1時間くらいでした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelアプリケーションをAWS上のDockerで動かす

概要

LaravelのアプリケーションをAWS上のDockerで動かすための環境を構築します。
具体的には、以下を行います。

  1. コンテナを構築するためのDockerfileを作成する
  2. ECRリポジトリの作成 → AWS CLIで作成
  3. 1.で作成したDockerイメージをECRにをpushする
  4. ECSの環境構築 → マネジメントコンソールから作成

前提条件

  • AWS CLIがインストールがインストールされていること

コンテナを構築

phpnginxの2つのコンテナを構築します。Dockerfileは、それぞれ下記の通りです。

phpコンテナのDockerfile

必要なソースコードをコンテナ内にコピーし、依存ライブラリのインストールを行なっています。

FROM php:7.2.10-fpm-alpine

COPY . .
WORKDIR /var/www/html

RUN set -x && \
  apk update && \
  apk add --no-cache libxml2 libxml2-dev curl curl-dev autoconf $PHPIZE_DEPS && \
  docker-php-ext-install opcache mysqli pdo pdo_mysql xml mbstring curl session tokenizer json && \
  curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer && \
  composer global require hirak/prestissimo && \
  composer install && \
  chmod -R a+w storage/ bootstrap/cache

COPY ./docker/php/config/php.ini /usr/local/etc/php/php.ini
COPY ./docker/php/config/docker-php-ext-opcache.ini /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini

nginxのDockerfile

default.confの設定を行なっています。
publicディレクトリ配下は、nginxコンテナに配置しています。

FROM nginx:1.15.5-alpine

ADD ./docker/nginx/config/default.conf /etc/nginx/conf.d/default.conf

RUN mkdir -p /var/www/html/public
ADD ./public/ /var/www/html/public

.dockerignoreの追加

Dockerのイメージを小さくするために、.dockerignoreを追加します。
node_modulesのような容量が大きいものは追加しておく方がいいと思います。

.dockerignoreについては、下記の記事に詳しく説明されていますので載せておきます。
.dockerignore アンチパターン

ECRにDockerイメージをpushする

ECRとは

ECR(Amazon Elastic Container Registry)は、Dockerのコンテナイメージを保存しておくためのレジストリサービスです。ECRに保存したコンテナイメージは、Amazon ECSへのデプロイが可能であったり、他の AWS サービスと簡単に連携することができます。

Amazon ECR

事前準備

AWS CLIを使用して、ECR用のプロファイルを作成します。

$ aws configure --profile ecr

リポジトリ作成

phpeginxの2つのリポジトリを作成します。

$ aws ecr create-repository --repository-name php --profile ecr
$ aws ecr create-repository --repository-name nginx --profile ecr

マネジメントコンソールで作成する場合は、以下の通りです。

Amazon ECR > Repositoriesを選択し、リポジトリの作成をクリックします。
スクリーンショット 2019-02-26 16.07.08.png

リポジトリ名を入力し、リポジトリの作成をクリックして、リポジトリを作成します。
スクリーンショット 2019-02-26 16.08.30.png

nginx/phpコンテナのビルド

上記で作成したリポジトリ名と同じになるように、dockerイメージ名を指定しビルドします。
タグにはlatestとしていますが、必要に応じて変更してください。

$ docker build -t {AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/php:latest -f docker/php/Dockerfile .
$ docker build -t {AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/nginx:latest -f docker/nginx/Dockerfile .

以下の通りDockerイメージが作成されているはずです。

$ docker image ls
REPOSITORY                                                TAG                 IMAGE ID            CREATED             SIZE
************.dkr.ecr.ap-northeast-1.amazonaws.com/php     latest              e7cc55e81372        23 seconds ago      376MB
************.dkr.ecr.ap-northeast-1.amazonaws.com/nginx   latest              c9bfc2840cbc        3 minutes ago       18.2MB

ECRにログイン

ECRにログインする為のパスワードを取得します。

$ aws ecr get-login --region ap-northeast-1 --no-include-email --profile ecr

以下のコマンドが表示されるので、全てコピペし、docker loginでログインします。

docker login -u AWS -p password https://{AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com

Login Succeededと表示されればログイン成功です。

~/.docker/config.jsonを確認するとauthsに追加されていることが確認できます。

~/.docker/config.json
"auths": {
        "{AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com": {}
    },

ECRへDockerイメージをpush

上記で作成したリポジトリにDockerイメージをpushします。

$ docker push {AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/php:latest
$ docker push {AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/nginx:latest

ECSの環境構築

セキュリティグループの作成

ECSを構築する前に、以下を実現するためのセキュリティグループを2つ作成します。

  • インターネットからALBの 443/tcp に対するアクセス許可
  • ALBからphpコンテナの 80/tcp に対するアクセス許可

ALBのセキュリティグループ

  • 外部から 443/tcp へのアクセス許可

以降の手順で作成するALBを割り当てます。

ECSサービスのセキュリティグループ

  • ALBのセキュリティグループから 80/tcp へのアクセス許可

ECSサービスのセキュリティグループとしてますが、実際にはECSでクラスターを作成する際にEC2インスタンスに割り当てるセキュリティグループです。

ALBを作成する

通常のALB作成手順に従ってください。
セキュリティグループは、上記で作成したALBのセキュリティグループを割り当てます。

ALBを利用せずにECSをPublicサブネットに配置する場合は、作成する必要はありません。

これ以降、ECSの構築を行なっていきます。

タスクを定義する

コンテナの起動方法をタスクの定義として登録します。
phpコンテナとnginxンテナの組み合わせを定義していきます。

Amazon ECS > タスク定義から、新しいタスク定義の作成を選択します。
起動タイプは、EC2を選択します。

タスクとコンテナの定義の設定

項目
タスク定義名 sample-api
タスクロール なし
ネットワークモード default

補足:
タスクロールは、コンテナ内から他のサービスにアクセスするためのロール設定です。
ネットワークモードについては、下記の記事にわかりやすくまとめられていましたので、掲載します。
ECSでEC2インスタンスを利用する際のネットワークモードについて調べてみた | DevelopersIO

次に、コンテナの定義で、phpコンテナとnginxコンテナを追加します。

phpコンテナを追加

項目
コンテナ名 php
イメージ {AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/php:latest
作業ディレクトリ /var/www/html
メモリ制限(MB) ハード制限 300(MB)

nginxコンテナを追加

項目
コンテナ名 nginx
イメージ {AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/nginx:latest
メモリ制限(MB) ハード制限 300(MB)
リンク php
ポートマッピング tcp 80:80

以上で設定は完了です。作成を押下して、タスクの定義を作成します。

ECSクラスタを作成する

Amazon ECS > クラスターから、クラスターの作成を選択します。
クラスターテンプレートの選択は、EC2 Linux + ネットワーキングを使用します。

インスタンスの設定

項目
クラスター名 sample-api-cluster
EC2 インスタンスタイプ t2.micro
プロビジョニングモデル スポット
スポットインスタンスの配分戦略 最低価格
インスタンス数 1
Maximum bid price (per instance/hour) 10($)

補足:

スポットインスタンスは中断される可能性があります。中断できないアプリケーションに対しては、スポットインスタンスを使用しないことをお勧めします。
クラスターの作成 - Amazon Elastic Container Service

sshログインを可能とする場合、キーペアを指定している必要があります。
この場合、セキュリティグループで22番ポートも解放してください。

ネットワーキング
VPCは、ALB作成で指定したVPC指定。
セキュリティグループは、作成済みのECSサービスのセキュリティグループを指定。

コンテナインスタンス IAM ロール
デフォルトで選択されているecsInstanceRoleを指定。

スポット群 IAM ロール
デフォルトで選択されているecsSpotFleetRoleを指定。

以上で設定は完了です。作成を押下して、クラスタを作成します。
クラスタが作成されると、コンテナが起動するEC2インスタンスが作成されます。

サービスを作成する

Amazon ECS > クラスター > サービスタブから、作成を選択します。

項目
起動タイプ EC2
タスク定義 sample-api
クラスタ sample-api-cluster
サービス名 sample-api
サービスタイプ REPLICA
タスクの数 1

Elastic Load Balancing(オプション)

項目
ELB タイプ Application Load Balancer
サービス用の IAM ロールの選択 デフォルトで選択されているecsServiceRoleを選択
ELB 名 作成したALBを選択

負荷分散用のコンテナ

nginx:80:80を指定し、ELBへの追加を選択。
ターゲットグループ名に、作成したALBに紐づくターゲットグループを選択を選択。

以上で、ECSの環境構築は完了です。

動作確認

作成したALBのDNS名にアクセスし、動作確認を行います。

Fargateに移行

この記事で作成した環境をFargateに変更する手順を簡単にまとめました。
ECSのバックエンドをEC2からFargateに変更

参考記事

下記を参考にさせていただきました。
【AWS】初めてのECR
LaravelアプリケーションをローカルでもAWSでもDockerで動かす

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

sam-cliを使ってKotlinでlambdaを書いた話

概要

最近流行りのsam-cliを使って、Kotlinでlambdaを書いてみました。
今回書いたのは簡単なGETとPOSTの処理になります。
完成物はこちら

環境

  • Kotlin1.3.20
  • Docker for Mac
  • SAM-CLI

開発

開発環境構築

sam-cliのインストール〜パッケージ作成

  1. brew tap aws/tap
  2. brew install aws-sam-cli
  3. sam init --runtime java8

mavenにKotlinの依存関係等の追記

  1. dependenciesに以下のライブラリを追加します。
    • kotlin-stdlib-jdk8
    • kotlin-test-junit
  2. pluginに以下のプラグインを追記します。
    • maven-shade-plugin
  3. sourceDirectory, testSourceDirectoryのpathをsrc/main/kotlin, src/test/kotlinに変更する。

完成品はこちら

javaのソースをKotlinに変換

こちらはintellijを使っていれば、自動でjavaからKotlinに変更することができます。

最初の動作確認

$ mvn package
$ sam local start-api

これでlocal環境でlambdaが立ち上がり、localhostでアクセスできるようになるのでまずは動くことを確認します。
最初は localhost:3000/helloHello World になるはずです。

POST用のlambdaの作成

今回は新たにリクエストボディに人の名前を詰めると、レスポンスにそれを含めてHello Worldを返すAPIを作成します。

受け取るRequestのclassの作成

今回はbodyに以下のようなJsonを渡します。


{
    "firstName": "太郎",
    "lastName": "山田"
}

次にリクエストを受け取るためのクラスを作成します。
ターミナルにて、以下のコマンドを実行するとlambdaが受け取るeventのjsonが取得できるので、それに沿ってクラスを作ります。

$ sam local generate-event apigateway aws-proxy --method POST

{
  "body": "eyJ0ZXN0IjoiYm9keSJ9",
  "resource": "/{proxy+}",
  "path": "/path/to/resource",
  "httpMethod": "POST",
  "isBase64Encoded": true,
  "queryStringParameters": {
    "foo": "bar"
  },
  "pathParameters": {
    "proxy": "/path/to/resource"
  },
  "stageVariables": {
    "baz": "qux"
  },
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate, sdch",
    "Accept-Language": "en-US,en;q=0.8",
    "Cache-Control": "max-age=0",
    "CloudFront-Forwarded-Proto": "https",
    "CloudFront-Is-Desktop-Viewer": "true",
    "CloudFront-Is-Mobile-Viewer": "false",
    "CloudFront-Is-SmartTV-Viewer": "false",
    "CloudFront-Is-Tablet-Viewer": "false",
    "CloudFront-Viewer-Country": "US",
    "Host": "1234567890.execute-api.us-east-1.amazonaws.com",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Custom User Agent String",
    "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
    "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
    "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
    "X-Forwarded-Port": "443",
    "X-Forwarded-Proto": "https"
  },
  "requestContext": {
    "accountId": "123456789012",
    "resourceId": "123456",
    "stage": "prod",
    "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
    "requestTime": "09/Apr/2015:12:34:56 +0000",
    "requestTimeEpoch": 1428582896000,
    "identity": {
      "cognitoIdentityPoolId": null,
      "accountId": null,
      "cognitoIdentityId": null,
      "caller": null,
      "accessKey": null,
      "sourceIp": "127.0.0.1",
      "cognitoAuthenticationType": null,
      "cognitoAuthenticationProvider": null,
      "userArn": null,
      "userAgent": "Custom User Agent String",
      "user": null
    },
    "path": "/prod/path/to/resource",
    "resourcePath": "/{proxy+}",
    "httpMethod": "POST",
    "apiId": "1234567890",
    "protocol": "HTTP/1.1"
  }
}

続いてこのJsonに対応するクラスを作っていきます。

class Request {
    lateinit var body: String |
}

data class Person(val firstName: String, val lastName: String)

Requestクラスの方は、 RequestHandler を継承したクラスでlambdaの関数を作成する場合、空のコンストラクタがないとパースできないので、今回は通常のclassにしています。
また、リクエストボディに関しては、Jsonではなく文字列で受け取るので、RequestクラスではString型で定義します。
今回はbody以外使わないので、classにはbody以外持たせていません。

lambdaの関数の実装

まずはJsonをパースするために jackson-module-kotlin をpom.xmlのdependencyに追記します。
続いていよいよ関数を実装していきます。
基本的な流れは自動生成されるgetのものと同様ですが、最初にリクエストボディをパースします。
実際のコードは以下の通りです。

class PostApp : RequestHandler<Request, GatewayResponse> {

    override fun handleRequest(input: Request, context: Context?): GatewayResponse {
        val headers = HashMap<String, String>()
        headers["Content-Type"] = "application/json"
        headers["X-Custom-Header"] = "application/json"

        val mapper = jacksonObjectMapper()
        val person = mapper.readValue<Person>(input.body)
        return try {
            val pageContents = this.getPageContents("https://checkip.amazonaws.com")
            val output = String.format("{ \"message\": \"hello %s\", \"location\": \"%s\" }", person.lastName, pageContents)
            GatewayResponse(output, headers, 200)
        } catch (e: IOException) {
            GatewayResponse("{}", headers, 500)
        }
    }

    @Throws(IOException::class)
    private fun getPageContents(address: String): String {
        val url = URL(address)
        BufferedReader(InputStreamReader(url.openStream())).use { br -> return br.lines().collect(Collectors.joining(System.lineSeparator())) }
    }
}

動作確認

$ mvn package
$ sam local start-api

これらを実行し、今回自分で作成したlambdaのエンドポイントを叩いてみます。

$ curl -X POST -d '{"firstName": "山田", lastName: "太郎"}' https://127.0.0.1:3000

これで期待したレスポンスが返ってくれば成功です!

最後に

「javaでかけるんだからKotlinでもかけるっしょ」の精神で書いてみました。
ただ書いてみて思うのは、あえてKotlinで書く必要性がそこまでなかったのではないだろうか。。。というものでした。
個人の趣味とか、会社で使うlambdaの一つをお試しで、とかなら良いかもしれません。

以上でsam-cliを使ってKotlinでlambdaを書いた話は終わりになります。
ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS-CDK for TypeScriptで色んなサービスをデプロイする

はじめに

皆様はAWSのサービスをデプロイするのをどうされていますか?

  • コンソール画面からGUIで操作して…
  • AWS-CLIで利用して…
  • CloudFormationのテンプレートファイルを書いて…
  • TerraformやServerlessFrameworkなどの構成管理ツールを使って…

などの方法があると思います。
デプロイするサービスが多くなればなるほど、構成管理ツールを用いたほうが管理コストがかからなくなるので良いですよね。
どのサービスをどれだけ、どのサービスと紐づけているのかをソースコードベースで確認することができます。
また不必要なサービスを減らすことができるのも利点の一つかと思います。

今回は awslabs がOSSとして開発している AWS-CDK を紹介していこうと思います。

AWS-CDKとは?

AWS-CDK(Cloud Development Kit)はCloudForamationのテンプレートファイルを、TypeScriptやJavaScript、Javaなどで書くことができるフレームワークです。
CloudForamationのテンプレートファイルはJSONまたはYAMLで書く必要があり、馴染みのないエンジニアにとっては学習コストが高く感じることがあると思います。
AWS-CDKではtsファイルやjsファイルをビルドするとCloudFormationのYAMLファイルが生成されます。
リリースされたのは去年の夏頃で、OSSで開発が進められています。(2019/02/28時点の最新バージョンは 0.24.1)
ただまだ開発者プレビューという状態なので、「これから大きな変更があるかもしれないよ」とドキュメント内でも書かれています。

この記事ではTypeScriptでの書き方を紹介していきます。
この記事で紹介したソースコードなどは こちらのGithubリポジトリにpushしています。こちらも合わせてご確認ください。

前提条件

  • Node.js >= 8.11.x
  • TypeScript => 2.7
  • AWSのCredentailの設定(参考)(AWS-CLIの初期設定ができていたらOK)

インストール

$ npm i -g aws-cdk
$ cdk --version
0.24.1 (build 67fcf6d)

初期設定

$ mkdir hello-cdk
$ cd hello-cdk
$ cdk init app --language=typescript

コマンド

// デプロイ
$ cdk deploy

// スタックを指定してデプロイ
$ cdk deploy ${StackName} 

// CloudFormationのテンプレートファイル生成
$ cdk synth

// CloudFormationのテンプレートファイル生成してファイルに書き出す
$ cdk synth --output ./output

// 差分を確認
$ cdk diff

今回デプロイしていくサービス

  • S3
  • DynamoDB
  • APIGateway
  • Lambda
  • IAM
  • Cognito

S3

Install Package

$ npm i @aws-cdk/aws-s3

SourceCode



```ts
import cdk = require("@aws-cdk/cdk");
import s3 = require("@aws-cdk/aws-s3");

export class CdkStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);

/**
 * Create S3 bucket
 */
new s3.Bucket(this, id, {
  bucketName: "bucketName"
})

}
}
```


DynamoDB

Install Pakcage

$ npm i @aws-cdk/aws-dynamodb

SourceCode


import cdk = require("@aws-cdk/cdk");
import dynamodb = require("@aws-cdk/aws-dynamodb");

export class CdkStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    /**
     * Create DynamoDB Table
     */
    new dynamodb.Table(this, id, {
        tableName: "tableName",
        partitionKey: {
          name: "HashKey",
          type: dynamodb.AttributeType.String // String or Number or Binary
        },
        sortKey: {
          name: "SortKey",
          type: dynamodb.AttributeType.String // String or Number or Binary
        }
      }
    )
  }
}


APIGateway & Lambda & IAM

Install Package

$ npm i @aws-cdk/aws-apigateway @aws-cdk/aws-lambda @aws-cdk/aws-iam

SourceCode

Webアプリケーションからのリクエストを受け取るRESTの受け口としてAPIGatewayを立てます。
また、CORSとCognitoUserPool認証を有効にしています。
LambdaはDynamoDBへの権限をいくつか付与し、APIGatewayからinvokeされるようにしています。


import cdk = require("@aws-cdk/cdk");
import apigateway = require("@aws-cdk/aws-apigateway");

export class CdkStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    /**
     * Create APIGateway
     */
    const api: apigateway.RestApi = new apigateway.RestApi(this, id, {
      restApiName: "apiName",
      description: "apiDescription"
    });

    /**
     * Create APIGateway Authorizer
     * Cognito認証の設定
     */
    const apiAuthorizer: apigateway.CfnAuthorizer = new apigateway.CfnAuthorizer(this, id, {
      restApiId: api.restApiId,
      name: "authorizerName",
      identitySource: "method.request.header.Authorization",
      providerArns: ["cognitoArn"],
      type: "COGNITO_USER_POOLS"
    });

    /**
     * Create Lambda Function
     */
    const getHandler: lambda.Function = new lambda.Function(this, id, {
      functionName: "functionName",
      runtime: lambda.Runtime.NodeJS810, // Lambdaのランタイム
      code: lambda.Code.directory("resources"), // Lambdaのソースコードを置いているディレクトリ
      handler: index.handler, // Lambdaのハンドラー
      environment: {
        TZ: "Asia/Tokyo" // 環境変数
      }
    });

    /**
     * Create IAM Policy Statement
     */
    const statement: iam.PolicyStatement = new iam.PolicyStatement().allow()
      .addActions(
        "dynamodb:PutItem",
        "dynamodb:UpdateItem",
        "dynamodb:Query",
        "dynamodb:Scan"
      )
      .addResource(
        "DynamoDBTableARN" // 操作の対象となるDynamoDBTableのARN
      );

    /**
     * Attach role to Lambda
     */
    getHandler.addToRolePolicy(statement);

    /**
     * Add GET method to APIGateway
     */
    const integration = new apigateway.LambdaIntegration(getHandler);
    const option: apigateway.MethodOptions = {
      authorizationType: apigateway.AuthorizationType.Cognito,
      authorizerId: authorizer.authorizerId
    }
    api.root.addMethod("GET", integration, option);

    /**
     * Active APIGateway CORS Setting
     * CORSの設定をするために OPTION メソッドを追加します
     */
    const options = api.root.addMethod("OPTIONS", new apigateway.MockIntegration({
      integrationResponses: [{
        statusCode: "200",
        responseParameters: {
          "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'",
          "method.response.header.Access-Control-Allow-Origin": "'*'",
          "method.response.header.Access-Control-Allow-Credentials": "'false'",
          "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST,DELETE'",
        }
      }],
      passthroughBehavior: apigateway.PassthroughBehavior.Never,
      requestTemplates: {
        "application/json": "{\"statusCode\": 200}"
      }
    }));
    const methodResource = (options as cdk.Construct).node.findChild("Resource") as apigateway.CfnMethod;
    methodResource.propertyOverrides.methodResponses = [{
      statusCode: "200",
      responseModels: {
        "application/json": "Empty"
      },
      responseParameters: {
        "method.response.header.Access-Control-Allow-Headers": true,
        "method.response.header.Access-Control-Allow-Origin": true,
        "method.response.header.Access-Control-Allow-Credentials": true,
        "method.response.header.Access-Control-Allow-Methods": true,
      }
    }];
  }
}


Cognito

Install Package

$ npm i @aws-cdk/aws-cognito

SourceCode


import cdk = require("@aws-cdk/cdk");
import cognito = require("@aws-cdk/aws-cognito");

export class CdkStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    /**
     * Create Cognito UserPool
     */
    const userPool: cognito.CfnUserPool = new cognito.CfnUserPool(this, id, {
      userPoolName: "UserPoolName",
      // パスワードポリシー
      policies: {
        passwordPolicy: {
          minimumLength: 8,
          requireLowercase: true,
          requireNumbers: true,
          requireUppercase: true,
          requireSymbols: false
        }
      },
      // 必須の標準属性やカスタム属性
      schema: [
        {
          name: "email",
          attributeDataType: "String",
          required: true
        }
      ],
      // 自動検証する項目
      autoVerifiedAttributes: ["email"],
      // Eメール検証メッセージの件名
      emailVerificationSubject: "Your verification code",
      // Eメール検証メッセージの内容
      emailVerificationMessage: "Your verification code is {####}"
    });

    /**
     * Create Cognito UserPool Client
     */
    new cognito.CfnUserPoolClient(this, id, {
      clientName:"UserPoolClientName",
      userPoolId: userPool.userPoolId
    });
}


まとめ

「TypeScriptからCloudFormationのYAMLが生成されるなんて素敵過ぎる!!!」と興奮気味な感じで色々触ってみましたが、実際最高でした。
YAMLファイルを書くのにあまり抵抗はないのですが、AWS-CDKで生成すると、自動で設定を保管してくれる部分もあったりするので、一からすべてを書かないといけないというわけではないです。
またドキュメントもまだまだ少なく、ドキュメント読むよりソースコード読んだほうが早い。みたいなことがあったりします。
前述したとおり、まだ開発者プレビューの段階ということなので、今後のアップデートをしっかり追って行くことが大事だと思います。
今後も他のサービスのデプロイを確認したりしていこうと思っています!
ではまた!!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MastodonをAWS+オレオレ証明書で動かしてみる

概要

自分の勉強がてらAWSインスタンスにMastodonを設定してみる。SSL必須のようなのだが、恒常的に動かすつもりがないので、オレオレ証明書でとりあえず動かしてみる。当然ながら、このまま利用するのはセキュリティ的に問題なのでご承知頂きたい。

参考サイト

以下のサイトの情報を参考にした。
Dockerで雑にMastodonを起動する方法
AWSでMastodonインスタンスを作るまで。自分まとめ
今何かと話題のマストドン(mastodon)鯖を自分用に無料で立てる方法
Docker+MacでMastodonインスタンスをローカルで構築する
自己署名証明書でnginxをSSL化

構築手順

1. AWSでEC2インスタンスを作成

手順は省略。
とりあえずOSはUbuntu18.04,インスタンスタイプはt2.medium,ボリュームサイズは20GBで設定。セキュリティグループはssh(22)とHTTPS(443)をインバウンドに設定しておく。

2. スワップファイルの作成

インスタンスタイプはt2.micro程度だとメモリが足りないようなので、スワップファイルを作成する。ただし、dockerコンテナのbuildに無茶苦茶かかるので、buildまではインスタンスタイプを高くしておく手もある。

$ sudo fallocate -l 4G /swapfile
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile
$ echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

3.ミドルウェアのインストール

必要なパッケージを入れていく。

$ sudo apt-get update
$ sudo apt-get install -y python3-pip unzip docker.io nginx
$ sudo pip3 install docker-compose

4.Mastodonを設定する

MastodonをGithubから取得する。とりあえず、ホームディレクトリ直下へ。
バージョンによって出来ることの差が激しいので構築するバージョンを固定する。
作成時点で最新のtagであるv2.7.3に切り替える。

$ git clone https://github.com/tootsuite/mastodon.git
$ cd mastodon
$ git checkout -b v2.7.3 refs/tags/v2.7.3

ここで、永続化するためにdocker-compose.ymlの編集する例が多いが、v2.7.3では既に編集済みだったので、初期状態のままで大丈夫。

永続化用のディレクトリを作っておく。作成場所はdocker-compose.ymlと同階層。

$ mkdir postgres
$ mkdir redis

Dockerイメージを作成する。インスタンスタイプによってはすごく時間がかかる。

$ docker-compose build

その間にSSLの設定用に秘密鍵とオレオレ証明書を作成する。

5.SSLの設定

オレオレ証明書の作成

opensslを使って証明書を作成する。Ubuntu18.04にはプレインストールされていたが、なければインストールする。

オレオレ証明書はオレオレ認証局(ca)から発行する。そのため、まずは認証局を作る必要がある。認証局の秘密鍵(ca.key)を作成してから、認証局の証明書(ca.crt)を作成する。
証明書の有効期限は10年 (3650日)とする。証明書作成は対話式なので好きな値を入れよう。未設定でも構わない。

$ mkdir certs
$ cd certs
$ openssl genrsa -out ca.key 2048
$ openssl req -new -x509 -days 3650 -key ca.key -out ca.crt

次にngnixに設定するオレオレサーバ証明書(mastodon.crt)を作成する。
秘密鍵(mastodon.key)を作って、その後認証局に依頼するための署名要求ファイル(mastodon.csr)を作成する。ここも対話式になるので、少なくともOUとCNは入力する。(ここではOU=example, CN=example.com)
最後に署名要求ファイルに認証局が署名してサーバ証明書(mastodon.crt)を作る。

$ openssl genrsa -out mastodon.key 2048
$ openssl req -new -key mastodon.key -out mastodon.csr
$ openssl x509 -req -days 3650 -CA ca.crt -CAkey ca.key -CAcreateserial -in mastodon.csr -out mastodon.crt

nginxの設定

nginxの設定ファイルを編集する。先ほど作成したサーバ証明書と秘密鍵は
ssl_certificate,ssl_certificate_keyにパス付で指定する。

$ sudo vim /etc/nginx/sites-enabled/default

以下の内容に入れ替える。

/etc/nginx/sites-enabled/default
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

server {
  listen 80;
  listen [::]:80;
  server_name localhost:3000;
  root /home/ubuntu/mastodon/live/public;
  # Useful for Let's Encrypt
  #location /.well-known/acme-challenge/ { allow all; }
  #location / { return 301 https://$host$request_uri; }
}

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name localhost:3000;

  ssl_protocols TLSv1.2;
  ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;

  ssl_certificate     /home/ubuntu/mastodon/certs/mastodon.crt;
  ssl_certificate_key /home/ubuntu/mastodon/certs/mastodon.key;

  keepalive_timeout    70;
  sendfile             on;
  client_max_body_size 80m;
  root /home/ubuntu/mastodon/live/public;

  gzip on;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_buffers 16 8k;
  gzip_http_version 1.1;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

  add_header Strict-Transport-Security "max-age=31536000";

  location / {
    try_files $uri @proxy;
  }

  location ~ ^/(emoji|packs|system/accounts/avatars|system/media_attachments/files) {
    add_header Cache-Control "public, max-age=31536000, immutable";
    try_files $uri @proxy;
  }

  location /sw.js {
    add_header Cache-Control "public, max-age=0";
    try_files $uri @proxy;
  }

  location @proxy {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";
    proxy_pass_header Server;

    proxy_pass http://127.0.0.1:3000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  location /api/v1/streaming {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";

    proxy_pass http://127.0.0.1:4000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  error_page 500 501 502 503 504 /500.html;
}

nginxを再起動する。

$ sudo systemctl start nginx

6.再びMastodonの設定

以下のコマンドを2回打って、出力された文字列をどこかに記録しておく。

$ cd ~/mastodon
$ docker-compose run --rm web rake secret

次に環境設定ファイルを作る。テンプレートがあるのでそれをコピーして作成する。

$ cp .env.production.sample .env.production
$ sudo vi .env.production

以下のSECRET_KEY_BASE, OTP_SECRETに先ほどの文字列を設定する。

.env.production
20 LOCAL_DOMAIN=localhost:3000
34 # Application secrets
35 # Generate each with the `RAILS_ENV=production bundle exec rake secret` task     (`docker-compose run --rm web rake secret` if you use docker compose)
36 SECRET_KEY_BASE=
37 OTP_SECRET=

Databaseを作成する。

$ docker-compose run --rm web rails db:migrate

アセットを作成する。

$ docker-compose run --rm web rails assets:precompile

Mastodonのコンテナを起動する。docker-compose psできちんと立ち上がっているか確認しよう。

$ docker-compose up
$ docker-compose ps
        Name                      Command                   State                 Ports
-------------------------------------------------------------------------------------------------
mastodon_db_1          docker-entrypoint.sh postgres    Up (healthy)
mastodon_redis_1       docker-entrypoint.sh redis ...   Up (healthy)
mastodon_sidekiq_1     /tini -- bundle exec sidekiq     Up
mastodon_streaming_1   /tini -- yarn start              Up (unhealthy)   127.0.0.1:4000->4000/tcp
mastodon_web_1         /tini -- bash -c rm -f /ma ...   Up (unhealthy)   127.0.0.1:3000->3000/tcp

Mastodonに接続する

以上で完了。ChromeかEdgeあたりのWebブラウザで接続する。
FireFoxはオレオレ証明書のサイトは見れないので他のブラウザを使う。

https://EC2インスタンスのパブリックDNS or パブリックIP/about
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MastodonをAWS+オレオレ証明書で動かしてみる

概要

自分の勉強がてらAWSインスタンスにMastodonを設定してみる。SSL必須のようなのだが、恒常的に動かすつもりがないので、オレオレ証明書でとりあえず動かしてみる。当然ながら、このまま利用するのはセキュリティ的に問題なのでご承知頂きたい。

参考サイト

以下のサイトの情報を参考にした。
Dockerで雑にMastodonを起動する方法
AWSでMastodonインスタンスを作るまで。自分まとめ
今何かと話題のマストドン(mastodon)鯖を自分用に無料で立てる方法
Docker+MacでMastodonインスタンスをローカルで構築する
自己署名証明書でnginxをSSL化

構築手順

1. AWSでEC2インスタンスを作成

手順は省略。
とりあえずOSはUbuntu18.04,インスタンスタイプはt2.medium,ボリュームサイズは20GBで設定。セキュリティグループはssh(22)とHTTPS(443)をインバウンドに設定しておく。

2. スワップファイルの作成

インスタンスタイプはt2.micro程度だとメモリが足りないようなので、スワップファイルを作成する。ただし、dockerコンテナのbuildに無茶苦茶かかるので、buildまではインスタンスタイプを高くしておく手もある。

$ sudo fallocate -l 4G /swapfile
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile
$ echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

3.ミドルウェアのインストール

必要なパッケージを入れていく。

$ sudo apt-get update
$ sudo apt-get install -y python3-pip unzip docker.io nginx
$ sudo pip3 install docker-compose

4.Mastodonを設定する

MastodonをGithubから取得する。とりあえず、ホームディレクトリ直下へ。
バージョンによって出来ることの差が激しいので構築するバージョンを固定する。
作成時点で最新のbranchであるv2.7.3に切り替える。

$ git clone https://github.com/tootsuite/mastodon.git
$ cd mastodon
$ git checkout v2.7.3

ここで、永続化するためにdocker-compose.ymlの編集する例が多いが、v2.7.3では既に編集済みだったので、初期状態のままで大丈夫。

永続化用のディレクトリを作っておく。作成場所はdocker-compose.ymlと同階層。

$ mkdir postgres
$ mkdir redis

Dockerイメージを作成する。インスタンスタイプによってはすごく時間がかかる。

$ docker-compose build

その間にSSLの設定用に秘密鍵とオレオレ証明書を作成する。

5.SSLの設定

オレオレ証明書の作成

opensslを使って証明書を作成する。Ubuntu18.04にはプレインストールされていたが、なければインストールする。

オレオレ証明書はオレオレ認証局(ca)から発行する。そのため、まずは認証局を作る必要がある。認証局の秘密鍵(ca.key)を作成してから、認証局の証明書(ca.crt)を作成する。
証明書の有効期限は10年 (3650日)とする。証明書作成は対話式なので好きな値を入れよう。未設定でも構わない。

$ mkdir certs
$ cd certs
$ openssl genrsa -out ca.key 2048
$ openssl req -new -x509 -days 3650 -key ca.key -out ca.crt

次にngnixに設定するオレオレサーバ証明書(mastodon.crt)を作成する。
秘密鍵(mastodon.key)を作って、その後認証局に依頼するための署名要求ファイル(mastodon.csr)を作成する。ここも対話式になるので、少なくともOUとCNは入力する。(ここではOU=example, CN=example.com)
最後に署名要求ファイルに認証局が署名してサーバ証明書(mastodon.crt)を作る。

$ openssl genrsa -out mastodon.key 2048
$ openssl req -new -key mastodon.key -out mastodon.csr
$ openssl x509 -req -days 3650 -CA ca.crt -CAkey ca.key -CAcreateserial -in mastodon.csr -out mastodon.crt

nginxの設定

nginxの設定ファイルを編集する。先ほど作成したサーバ証明書と秘密鍵は
ssl_certificate,ssl_certificate_keyにパス付で指定する。

$ sudo vim /etc/nginx/sites-enabled/default

以下の内容に入れ替える。

/etc/nginx/sites-enabled/default
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

server {
  listen 80;
  listen [::]:80;
  server_name localhost:3000;
  root /home/ubuntu/mastodon/live/public;
  # Useful for Let's Encrypt
  #location /.well-known/acme-challenge/ { allow all; }
  #location / { return 301 https://$host$request_uri; }
}

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name localhost:3000;

  ssl_protocols TLSv1.2;
  ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;

  ssl_certificate     /home/ubuntu/mastodon/certs/mastodon.crt;
  ssl_certificate_key /home/ubuntu/mastodon/certs/mastodon.key;

  keepalive_timeout    70;
  sendfile             on;
  client_max_body_size 80m;
  root /home/ubuntu/mastodon/live/public;

  gzip on;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_buffers 16 8k;
  gzip_http_version 1.1;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

  add_header Strict-Transport-Security "max-age=31536000";

  location / {
    try_files $uri @proxy;
  }

  location ~ ^/(emoji|packs|system/accounts/avatars|system/media_attachments/files) {
    add_header Cache-Control "public, max-age=31536000, immutable";
    try_files $uri @proxy;
  }

  location /sw.js {
    add_header Cache-Control "public, max-age=0";
    try_files $uri @proxy;
  }

  location @proxy {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";
    proxy_pass_header Server;

    proxy_pass http://127.0.0.1:3000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  location /api/v1/streaming {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";

    proxy_pass http://127.0.0.1:4000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  error_page 500 501 502 503 504 /500.html;
}

nginxを再起動する。

$ sudo systemctl start nginx

6.再びMastodonの設定

以下のコマンドを2回打って、出力された文字列をどこかに記録しておく。

$ cd ~/mastodon
$ docker-compose run --rm web rake secret

次に環境設定ファイルを作る。テンプレートがあるのでそれをコピーして作成する。

$ cp .env.production.sample .env.production
$ sudo vi .env.production

以下のSECRET_KEY_BASE, OTP_SECRETに先ほどの文字列を設定する。

.env.production
20 LOCAL_DOMAIN=localhost:3000
34 # Application secrets
35 # Generate each with the `RAILS_ENV=production bundle exec rake secret` task     (`docker-compose run --rm web rake secret` if you use docker compose)
36 SECRET_KEY_BASE=
37 OTP_SECRET=

Databaseを作成する。

$ docker-compose run --rm web rails db:migrate

アセットを作成する。

$ docker-compose run --rm web rails assets:precompile

Mastodonのコンテナを起動する。docker-compose psできちんと立ち上がっているか確認しよう。

$ docker-compose up
$ docker-compose ps
        Name                      Command                   State                 Ports
-------------------------------------------------------------------------------------------------
mastodon_db_1          docker-entrypoint.sh postgres    Up (healthy)
mastodon_redis_1       docker-entrypoint.sh redis ...   Up (healthy)
mastodon_sidekiq_1     /tini -- bundle exec sidekiq     Up
mastodon_streaming_1   /tini -- yarn start              Up (unhealthy)   127.0.0.1:4000->4000/tcp
mastodon_web_1         /tini -- bash -c rm -f /ma ...   Up (unhealthy)   127.0.0.1:3000->3000/tcp

Mastodonに接続する

以上で完了。ChromeかEdgeあたりのWebブラウザで接続する。
なお、FireFoxはオレオレ証明のSSLは出来ないので他のブラウザを使う。

https://EC2インスタンスのパブリックDNS or パブリックIP/about

本格的に運用する時は、正式なドメイトとSSL証明書を取得しよう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Systems Manager のセッションマネージャーの小ネタ(Switchロールした際のログ表示、sudoの禁止)

はじめに

AWS Systems Manager のセッションマネージャーは、S3とCloudWatchLogsにログ出力できますが、Switchロールした場合は、どういった表示になるのか認識できてなかったので確認しました。ついでにsudoの禁止方法についても記載します。

Switchロールした場合のログ表示の確認

結論

Switchロール前のIAMユーザ名でセッションIDが表示されます。

実機確認

Switchロールする前のIAMユーザ(miyamoto)が表示されています。
image.png

CloudWatchLogsにも元のアカウントのIAMユーザ名がロギングされます。

Script started on 2019-02-28 01:47:39+0000
[?1034hsh-4.2# /usr/bin/ssm-session-logger /var/lib/amazon/ssm/i-030874ac1ad0d6f6d/sess 
ion/orchestration/miyamoto-08d0033a7ae098d22/Standard_Stream/ipcTempFile.log fal 
se
Error occurred fetching the seelog config file path: open /etc/amazon/ssm/seelog.xml: no such file or directory
Initializing new seelog logger
New Seelog Logger Creation Complete
[?1034hsh-4.2$ 
[Ksh-4.2$ 
sh-4.2$ 
sh-4.2$ 
sh-4.2$ 
sh-4.2$ 

sudoの禁止するには?

/etc/sudoers.d/ssm-agent-usersでssm-userに許可を削除すればSudoできる権限を剥奪可能です。

sh-4.2$ sudo cat /etc/sudoers.d/ssm-agent-users
# User rules for ssm-user
ssm-user ALL=(ALL) NOPASSWD:ALL

お約束

投稿内容は私個人の意見であり、所属企業・部門見解を代表するものではありません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JAWS DAYS 2019に行ってきた件(AI/ML系を中心に)

JAWS DAYS 2019に行ってきたので、備忘録です。

IMG_5093.JPG

総評:とにかく盛り上がってた

1900人超来て史上最高の人数だったそう。
あんま写真撮ってないんですが、確かにどのセッションも黒山の人だかり。
お弁当もランチカーも長蛇の列でした。
運営のみなさんお疲れ様です。

スポンサーもいっぱい。

IMG_5094.JPG

うーん、AWSとも仲良くしてるはずの実行委員長所属会社がスポンサーになってないのはなぜだ。

発表の傾向と回ったところ

当日のタイムテーブルはこちら。
https://jawsdays2019.jaws-ug.jp/timetable/

資料のまとめはこちら。
https://qiita.com/hayao_k/items/91c19480948f26b71705

実に11トラック。更に展示/同人誌ブースもあり、非常に盛りだくさん、満漢全席の名前の通りという感じでした。
満漢すぎて食いきれないので、2日に分けてほしいとも思ったくらいですが、まあ2日に分けると大変だろうしなぁ・・・

Serverless、コンテナ、ハイブリッドクラウドといった基盤周りの新しい軸に関するセッションが結構多かった印象。
IoTとかもちらほらありましたね。SORACOMのハンズオンとか楽しそうだった。
私はAI/ML系のセッションを中心に聞いてきました。
以下、気になったセッションをいくつか紹介。

気になったセッション

この3セッションが連続したあたりが、個人的なハイライトでした。
ここに書いてないことも色々と話されていたのですが、気になったところを中心に。

[AI/ML 前半] PythonとSageMakerで始める MLチームのみで完結するAPIの構築事例 GVA TECH 武田さん

https://speakerdeck.com/ababa893/jaws-days-2019-pythontosagemakerteshi-merumltimufalsemitewan-jie-suruapifalsegou-zhu-shi-li

  • ML機能の開発フローは、「分析検証→アルゴリズム開発→学習・モデル変換→APIのデプロイ→プロダクトに反映」
    • プロダクトに反映まで行って初めてユーザ価値が生まれる。なので、そのスピードをなるべく高速化したい。
  • APIとしてデプロイしてプロダクトに反映するところはインフラまわりのスキルが必要。
  • 機械学習の専門家集団であるMLチームは基本的にインフラ周りのスキルはそんなに高くない。彼らにそこをやらせるのはスキルの無駄遣い。
  • 一方、インフラ周りの担当を別途用意すると、MLチームとは文化が違ってわかりあうのが難しい。(MLチームのコードがわんぱくすぎる、とか)
    • コミュニケーションコストが高くなって、結局速くならない。
  • そこで、SageMakerを活用して、MLチームのみで商用環境へのデプロイ可能な状況を作った。
  • 結果、新バージョンのAPIリリースがほぼ1日で対応可能に!

[AI/ML] 機械学習における AWS を用いたマイクロサービスアーキテクチャ ABEJA 中川さん

https://www.slideshare.net/yutanakagawa2/aiml-aws

  • AI/ML業界は技術の進化スピードがえげつない。 
  • だから、ビジネスとしては速攻で最新のモデルを取り入れていきたい。
  • ところが分析系機能のアーキテクチャが完全にモノリスで、1年位モデルいれかえてない・・・とかいう状況に。
  • そこで、モデル部分をマイクロサービスにして、影響範囲を極小化しながら必要なモデルを簡単に差し替えられるようにした。
    • ついでに、モデルのフローも組み換え可能に。
  • これによって、最新の技術を論文で知り、検証用コードを活用して新たなモデルを作り、すぐにプロダクトに組み込みが可能に。

[BigData] AI/MLシステムにおけるビッグデータとの付き合い方 DeNA 鈴木さん

  • AI/ML系の開発をスムーズ・迅速に行うために、3つのロールを設けている。
    • AI研究開発エンジニア - 各分野の専門性を持ってる人。研究のウォッチと活用。
    • データサイエンティスト - Kagglerの人たち。泥臭い系。
    • MLエンジニア - AI技術をサービス上に実装して安定運用を実現するのがミッション。←鈴木さんはここ。
  • MLエンジニアはモデル開発以外の部分は何でもやる。AI研究開発エンジニアやデータサイエンティストは基盤周り弱いので。
    • 機械的なレベルのデータクレンジングとかも全部やってる
    • セキュリティ周りも、普通にやってれば問題が発生しないように整備しておく
    • 野良インスタンスが立たないようにとか、GPUインスタンスのスポットインスタンスを使うとか、そのへんもお手伝い。
      • SageMakerはスポットインスタンス使えなくて高いのもあるし、意外とニーズがなかった。

所感

というわけで、如何に専門性の高い人のスキルを無駄遣いせずに、彼らのアイデアを最短距離でビジネスに落とし込んでいくかというところが一つのキーポイントなのだな、という感じですね。
でも、そこに向けての工夫ややり方、体制は三者三様。専門家がデプロイまで簡単にできるようにしてあげるのか、そこは他の人が見てあげるのか。デプロイの高速化のやり方もそれぞれ。この辺は組織やメンバーにあったやり方をそれぞれ模索するべきなのでしょう。
他にも色々セッションは聞いたんですが、この3つが一番印象に残ったので取り上げてみました。

あといちばん笑ったのは営業セッションだったけどそれはまたどこかで。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS LambdaのPHP関数のログ出力について

はじめに

前回の AWS Lambda のCustom RuntimeでPHP用のLayer作成 で作成したLayerを使用します。
(Stackery社のlayerでも問題ないです。)

作業環境

LayerのZip作成にdockerを、AWSへのアップロードにaws-cliを使います。

  • pyenv
    • 1.2.9-2
  • pip
    • 9.0.3(python3.6.5)
  • aws-cli
    • 1.16.96(python3.6.5)
  • sam
    • 0.14.2

作業はMacOSです。
各種インストールは割愛します。

使用ソースコード

https://github.com/Sunochi/LambdaTestFuntion

ログ出力について

AWS LambdaでPHPを使用した際の挙動として、ログ出力が不便という点があります。
API Gatewayを使用する場合はブラウザ上にvar_dump()で表示すれば良いですが、データとして残すためにもCloudwatch logsへ出来る限り可視性が高い状態で出力しておくことが望まれます。
最近はcloudwatch logsの機能で、json形式のログから検索も可能になっているようなので、ログ出力はちゃんとしようと思う毎日です。
さて、今回はprintfvar_dumperror_logの挙動の違い、現時点でのPHPでのログ出力方法のベストプラクティスをまとめておきます。

テストコード

index.php
<?php 
function print_log(){
    $temp_array = [
        'hoge' => "foo",
        'var'  => 2,
    ];
    printf("Hello World!\nhogehoge");
    printf($temp_array);
}

function var_dump_log(){
    $temp_array = [
        'hoge' => "foo",
        'var'  => 2,
    ];
    var_dump("Hello World!\nhogehoge");
    var_dump($temp_array);
}

function error_log_log(){
    $temp_array = [
        'hoge' => "foo",
        'var'  => 2,
    ];
    error_log("Hello World!\nhogehoge");
    $json_temp_array = json_encode($temp_array);
    error_log($json_temp_array);
}

function main(){
    print_log();
    var_dump_log();
    error_log_log();
}

結果

error_log()json_encodeの組み合わせが最適です。

printf

Lambda上のログ詳細: "Hello World!\nhogehogeArray"

CloudWatch logs: Hello World!
<行切り替え>hogehogeArray

printfで表示は可能ですが、複数のログを出力した際に、
1行にまとめて表示される問題があります。
\nがCloudwatch logsでの行切り替えになっているため、想定通りの出力にならないことがありそうです。

var_dump

Lambda上のログ詳細: "string(21) \"Hello World!\nhogehoge\"\narray(2) {\n  [\"hoge\"]=>\n  string(3) \"foo\"\n  [\"var\"]=>\n  int(2)\n}\n"

CloudWatch logs: string(21) "Hello World!
<行切り替え>hogehoge"
<行切り替え>array(2) {
<行切り替え>["hoge"]=>
<行切り替え>string(3) "foo"
<行切り替え>["var"]=>
<行切り替え>int(2)
<行切り替え>}

実際に出力してみるとわかりますが、とてもログを参考にできたものはないです。ブラウザ上に表示する場合はvar_dump()がいいと思いますが、ログ出力には向いていないのがわかると思います。

error_log

error_logにArrayを入れるとwarningが出るため、先にjson_encodeしておきます。()
```
Lambda上のログ詳細: ""

CloudWatch logs: Hello World!
<行切り替え>hogehoge
<行切り替え>
{
"hoge": "foo",
"var": 2
}
```

printf, var_dumpと比べて、行切り替えのタイミングが大分よくなりました。
ただ、\nは行切り替えしたくないので、以下のラッパー関数を作成します。

logging.php
function logging ($output) {
    $log = "";

    if(is_array($output)){
        $log = json_encode($output);
        $log = str_replace("\n", "\\n", $log);
    } else {
        $log = str_replace("\n", "\r", $log);
    }
    error_log($log);
}

結果

Lambda上のログ詳細: ""

CloudWatch logs: Hello World!
hogehoge
<行切り替え>
{
    "hoge": "foo",
    "var": 2
}

いいですね!Lambda上のログ詳細に出したい場合(デバッグ時)は、
var_dumpを使用すればいいと思います。

まとめ

PHPのログ出力について、ログ関数を入れてまで本格的にやりたくないので、手軽にできる方法を探しました。
今のところ、error_logのラッパー関数が使い心地がいいですが、よりオススメの方法があれば知りたいです。

参考

AWS Lambda Custom RuntimeでPHPからCloudWatch Logsに出力する方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【メモ】ローカルからAWSのEC2にフォルダを転送する際に詰まったところ

背景

AWSのEC2にファイル転送するときに少し詰まったのでメモ

フォルダ転送コマンド

キーのあるディレクトリに移動し以下のコマンドを実行すればローカルフォルダを転送できる

$ scp -i [.pemファイルの場所].pem -r [転送元のローカルフォルダの場所] [ユーザー名]@[パブリックDNSかパブリックIP]:[転送先の場所]

・オプションの意味
-i :接続に使用する公開鍵ファイルを指定する
-r :ディレクトリ内を再帰的にコピーする

エラー

以下のようなエラーが出た

Permission denied (publickey). 

解決策

AWSで作成したインスタンスのオーナーは”ec2-user”(Amazon Linux の場合)しかし、デフォルトではアクセス権限がないため、”ec2-user”(オーナー)に書き込み権限を与えることで解決した

awsインスタンスにssh接続して以下のコマンドを実行し書き込み権限を与える

sudo chown -R ec2-user [転送先]

参考文献

エラー「Permission denied.」と「No such file or directory.」の判別方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む