カテゴリー: AWS Page 2 of 3

AWS Lambda関数をKotlin/JSで書いてみる

f:id:seeds-std:20190925123639p:plain

WEB事業部の西村です。

唐突ですが皆さんはLambda関数を作成する際にどのような言語を用いていますでしょうか?
用途に合った言語や自分で書きやすいと思う言語などの要因で決めていると思われます
私はKotlinという言語が好きでKotlinで書こうと思ったのですが、Kotlinで書かれている記事はそのほとんどがJavaで実行することを前提とした元なっています
しかし、Javaではコールドスタートした際に実行時間がかかってしまうというデメリットがあります
KotlinはJavaだけでなくJavaScriptでも動作させることができるので今回はNode.jsで実行させる関数の作成を行いたいと思います

目次

この記事の目標

Kotlin/JSを用いたLambda関数の作成とそのレスポンス確認

この記事で取り扱わないこと

この記事では AWS LambdaAmazon API Gateway については詳しく解説いたしません
あらかじめLambda関数とその関数を呼び出すためのAPIの作成を行っておいてください

開発環境

この記事では下記の環境で開発を行っています

  • AdoptOpenJDK 1.8.0_222-b10
  • IntelliJ IDEA Community 2019.2.2
  • Kotlin 1.3.50

プロジェクトの作成

プロジェクトの作成を行います
任意のディレクトリにプロジェクト用のフォルダを作成します(この記事では KotlinLambda としています)
その後そのディレクトリ内に build.gradle.ktssettings.gradle.kts の2つのファイルを作成します

KotlinLambda
├─build.gradle.kts
└─settings.gradle.kts

作成したらIntelliJ IDEAでプロジェクトのフォルダを開きます
続いて、 build.gradle.ktssettings.gradle.kts を開き下記の内容を入力します

build.gradle.kts

plugins {
kotlin("js") version "1.3.50"
}
repositories {
mavenCentral()
}
kotlin {
target {
nodejs()
useCommonJs()
}
sourceSets["main"].dependencies {
implementation(kotlin("stdlib-js"))
}
}

settings.gradle.kts

rootProject.name = "kotlin_lambda"

その後画面右下に出ている Gradle projects need to be importedImport Changes をクリックします
CONFIGURE SUCCESSFUL と出てきたら準備完了です

ハンドラソースコードの追加

次にソースディレクトリを作成します
プロジェクトのディレクトリに src/main/kotlin でディレクトリを作成します
またパッケージ名を追加する場合、パッケージを作成します(この記事では jp.co.seeds_std.lambda.kotlin とします)
作成したディレクトリに handler.kt というファイルを作成し下記の内容を記述します

handler.kt

package jp.co.seeds_std.lambda.kotlin
@JsExport
@JsName("handler")
fun handler(event: dynamic, context: dynamic, callback: (Error?, dynamic) -> Unit) {
val response: dynamic = object {}
response["statusCode"] = 200
response.body = "Hello from Kotlin Lambda!"
callback(null, response)
}
  • @JsExport JavaScriptのexportを行うアノテーションです
  • @JsName(name: String) 記述したクラスや関数をトランスパイルした際 name に指定した名前となるよう変換してくれるようになります
  • dynamic JavaScriptのオブジェクトをそのまま取り扱います(. [] でその中の関数や変数にアクセスできます)

デプロイ

デプロイパッケージの作成を行います
圧縮を自動化するためのタスクを追加します
build.gradle.kts を再度開き下記の内容を追加します

build.gradle.kts

import com.google.gson.JsonParser
import java.io.OutputStream
import java.util.zip.ZipOutputStream
import java.util.zip.ZipEntry
plugins { ... }
repositories { ... }
kotlin { ... }
open class CompressTask : DefaultTask() {
lateinit var buildDirectory: File
lateinit var projectName: String
var rootProjectName: String? = null
private val blackLists = setOf("kotlin-test-nodejs-runner", "kotlin-test")
@TaskAction
fun compress() {
val projectPrefix = if(rootProjectName != null && rootProjectName != projectName) "$rootProjectName-" else ""
val zipFile = File(buildDirectory, "../compress.zip")
val outputStream = ZipOutputStream(zipFile.outputStream(), Charsets.UTF_8).apply {
setLevel(9)
}
val jsDir = File(buildDirectory, "js")
val projectDir = File(jsDir, "packages/$projectPrefix$projectName")
val nodeModuleDir = File(jsDir, "node_modules")
addDependencies(outputStream, File(projectDir, "package.json"), nodeModuleDir, mutableSetOf(*blackLists.toTypedArray()))
addZipEntry(outputStream, File(projectDir, "kotlin/$projectPrefix$projectName.js"))
outputStream.close()
}
private fun addZipEntry(zipOutputStream: ZipOutputStream, file: File, addDirectory: String = "") {
val name = if(addDirectory.isEmpty()) file.name else "$addDirectory/${file.name}"
if(file.isDirectory) {
file.listFiles()?.forEach {
addZipEntry(zipOutputStream, it, name)
}
} else {
val zipEntry = ZipEntry(name)
zipOutputStream.addZipEntry(zipEntry) {
it.writeFile(file)
}
}
}
private fun addDependencies(zipOutputStream: ZipOutputStream, packageJsonFile: File, nodeModuleDir: File, addedDependencies: MutableSet<String> = mutableSetOf()) {
val packageJson = JsonParser().parse(packageJsonFile.reader()).asJsonObject
if(!packageJson.has("dependencies")) return
val dependencies = packageJson.getAsJsonObject("dependencies")
dependencies.keySet().forEach {
if(!addedDependencies.contains(it)) {
addedDependencies.add(it)
val dependenciesDir = File(nodeModuleDir, it)
addZipEntry(zipOutputStream, dependenciesDir, "node_modules")
addDependencies(zipOutputStream, File(dependenciesDir, "package.json"), nodeModuleDir, addedDependencies)
}
}
}
private inline fun ZipOutputStream.addZipEntry(entry: ZipEntry, crossinline block: (OutputStream) -> Unit) {
try {
putNextEntry(entry)
block(this)
} catch (e: Exception) {
e.printStackTrace()
} finally {
closeEntry()
}
}
@Suppress("NOTHING_TO_INLINE")
private inline fun OutputStream.writeFile(file: File) {
file.inputStream().use {
it.copyTo(this)
}
}
}
tasks.register("noMainCall") {
doFirst {
kotlin.target.compilations.all { compileKotlinTask.kotlinOptions.main = "noCall" }
}
}
tasks.register<CompressTask>("compress") {
dependsOn("compileKotlinJs", "noMainCall")
this.buildDirectory = rootProject.buildDir
this.projectName = project.name
this.rootProjectName = rootProject.name
}
tasks["compileKotlinJs"].mustRunAfter("noMainCall")

追加後、画面右下に出ている Gradle projects need to be importedImport Changes をクリックします
CONFIGURE SUCCESSFUL と出てきたら画面右側の Gradle タブを開きます
kotlin_lambda -> Tasks -> other の順に開き、その中の compress をダブルクリックして実行します
するとプロジェクトのディレクトリに compress.zip というファイルが生成されます
この生成されたファイルをコンソールからアップロードします
その後の ハンドラkotlin_lambda.{パッケージ名}.handler に変更します (この記事では kotlin_lambda.jp.co.seeds_std.lambda.kotlin.handler となります)
アップロードと変更が完了したら保存を行います

動作確認

あらかじめ作成しておいたAPI GatewayのURLにアクセスし Hello from Kotlin Lambda! と表示されれば成功です

最後に

いかがでしたでしょうか
Javaの時より動作も軽くなったと感じるのではないかと思います
次回はもう少し使いやすくなるような記事も書きたいと思います
ここまで読んでいただきありございました

Kotlin/JSのAWS Lambda関数を便利にしてみる

Systems Manager セッションマネージャーを利用したEC2へのリモート接続

クラウド事業部の上野です。

AWSのプライベートなネットワーク(インターネット上から直接アクセスできないネットワーク)上に立てたEC2インスタンスへのsshアクセスはどのように行われていますでしょうか?よくある構成としてはパブリックなネットワーク(インターネット上からアクセスできるネットワーク)上にあるEC2インスタンスを経由してアクセスといったものがあります。俗に踏み台サーバやBastionサーバと呼ばれるものです。弊社でも踏み台サーバを用意することが多いですね。

ところがAWSにはEC2インスタンスへのアクセスをサポートするSystems Manager セッションマネージャーという機能があります。
これはEC2にインストールされているSSM エージェントを利用してリモート接続を行います。SSMエージェントを利用すればネットワーク的に接続できないEC2インスタンスへ接続することも可能ですし、sshを使わないのでsshdのサービスを停止してもアクセスすることができるという優れものです。長年サーバ管理者をやっていますが、sshdを止めれる日が来るなんて思いもしなかったです。

それでは試してみましょう。
プライベートネットワーク上にEC2インスタンスを起動し、セッションマネージャーを利用してアクセスするということをやってみたいと思います。

まず、AWSアカウント作成後に標準で準備されているVPCにプライベートネットワークを用意します。

f:id:seeds-std:20190917124214p:plain
ssm01

このプライベートネットワーク上にEC2インスタンスを立てます。このインスタンスはパブリックIPを持たないため外部から直接ssh接続することはできません。

f:id:seeds-std:20190917124322p:plain
ssm02

次にSystems Manager をEC2が利用できるようにIAMロールを作成してEC2インスタンスに設定します。
ロールに設定するポリシーは AmazonEC2RoleforSSM になります。

これで準備が整いました。では早速インスタンスに接続してみましょう。
AWSマネジメントコンソールより、Systems Manager -> セッションマネージャーを開きます。
セッションの開始というボタンを押すとインスタンスの一覧が表示されます。インスタンスを選択し、セッションの開始ボタンを押します。

f:id:seeds-std:20190917124836p:plain
ssm04

するとWebブラウザでコンソールの画面が開きます。
ユーザはssm-userというユーザで接続されているようですね。sudoコマンドを使ってroot権限得ることもできます。

f:id:seeds-std:20190917125109p:plain
ssm05

それでは試しにsshdのサービスを止めてみましょう。
リモートからsshdを止めるなんていう暴挙は初めてです。sshdを停止してもアクセスできていることが確認できます。

f:id:seeds-std:20190917125237p:plain
ssm09

セッションの履歴からどのIAMユーザで接続されたかを確認することができます。

f:id:seeds-std:20190917140113p:plain
ssm06

セッションの詳細な情報をログとして出力することも可能です。
CloudWatch Logsに出力するように設定するとこのようになります。実行されたコマンド一つ一つまで記録されていますね。

f:id:seeds-std:20190917135950p:plain
ssm007

S3にファイルとして保管させることも可能です。

f:id:seeds-std:20190917140412p:plain
ssm08

いかがでしたか。

比較的容易にセッションマネージャーを使ってアクセスすることができました。
セッションマネージャーのいいところは踏み台サーバが不要であるということだけではなく、EC2インスタンスへのアクセスをIAMユーザで管理できるということにあります。
ログもコマンドレベルで出力されていますので、いざという時の監査ログとしても利用できそうですね。
今後は踏み台サーバではなく、セッションマネージャーを使うことも考慮に入れていきたいと思います。

AWS サーバレスでlibreofficeを使ってExcelをPDF変換

クラウド事業部の川勝です。

弊社のプロジェクトのいくつかでは、帳票のPDF出力にExcelで作成したものをPDF変換かけて使用するというものがあります。
Excel->PDF変換にはlibreoffceのheadlessモードを使用しているのですが、今回その仕組みを汎用的に使えるようにサーバレスで構築してみたのでご紹介いたします。

要件

  • ExcelファイルをWEB APIにPOSTしたらPDF変換されて返ってくる
  • 今回はbase64エンコードしたデータをjsonでやり取りする方式(理由は後述)

構成

f:id:seeds-std:20190827191927p:plain

Serverless libreoffice

まずAWS Lambdaでlibreofficeが使えるのか?(外部コマンドあるか)というところからはじめました。
外部コマンドでsofficeがあるかどうか。
ちょっと古い2014年の記事ですがどうもなさそうです…

AWS Lambdaをいろいろ暴く – Qiita

(ちなみにamazon linuxベースとのことなので大体の外部コマンドは使用可能ですね。)

自力でインストールするしかない、、できるのか、、?と調べていたところ以下を発見いたしました。

serverless-libreoffice/STEP_BY_STEP.md at master · vladgolubev/serverless-libreoffice · GitHub

こちらのStep:1ではlibreofficeをコンパイルするところから始まっていますが、リポジトリ内にコンパイル済みのファイルがありますのでそちらを使用します。(Step:2)

S3にserverless-libreofficeをアップロード

$ aws s3api create-bucket --bucket lambda-libreoffice-demo
$ curl -L https://github.com/vladgolubev/serverless-libreoffice/releases/download/v6.1.0.0.alpha0/lo.tar.gz -o lo.tar.gz
$ aws s3 cp lo.tar.gz s3://lambda-libreoffice-demo/lo.tar.gz --acl public-read-write

これで前準備は完了。 

Lambda実行時にアップロードしたファイルを展開して使用できるようにします。
今回はgoで実装していきます。

// main.go
func init() {
cmd := "curl https://" + os.Getenv("S3_BUCKET_NAME") + ".s3.amazonaws.com/lo.tar.gz -o /tmp/lo.tar.gz && cd /tmp && tar -xf /tmp/lo.tar.gz"
exec.Command("sh", "-c", cmd).Run()
}

main.goのinitでs3から取得したファイルを /tmp に展開します。
initで呼び出すことでlambdaコンテナの起動時のみ実行されます。
注意点はLambdaでは /tmp 以下にしか書き込み権限がないのと容量制限(500MB)があります。

よくある質問 – AWS Lambda |AWS

Q: AWS Lambda 関数のためにディスクにスクラッチスペースが必要な場合はどうすればよいですか?

各 Lambda 関数では /tmp ディレクトリに 500 MB の容量 (非永続型) を割り当てることができます。

ひとまずこれで /tmp/instdir/program/soffice として実行可能になります。
受け取ったExelファイルは /tmp/sample.xlsx にファイルとして保存しているとして、以下の様な感じで実行できました。

const convertCommand = "/tmp/instdir/program/soffice --headless --invisible --nodefault --nofirststartwizard --nolockcheck --nologo --norestore --convert-to pdf --outdir /tmp %s"
command := fmt.Sprintf(convertCommand, "/tmp/sample.xlsx")
exec.Command("sh", "-c", command).Run()

/tmp/sample.pdf が生成されるので、これをbase64エンコードしてjson形式で返却します。

…がしかし、実行はできましたが、このままだと日本語が文字化けてしまいます。
日本語フォントに対応が必要そうです。

日本語フォント

「lambda 日本語フォント」とかで調べていると以下の記事にあたりました。

AWS LambdaでPhantomJS日本語フォント対応 | RCO Ad-Tech Lab Blog

Lambdaの実行環境にフォントを追加する – Qiita

ようはfont-cache生成できればよさそうです。
上記記事では生成したfont-cacheをデプロイパッケージに含めていましたが、initでlibreofficeの展開もしているのでそこでfont-cache生成したらいいかな、と思ったのでその方向で実装しました。

initに追加します。

// main.go
func init() {
cmd := "curl https://" + os.Getenv("S3_BUCKET_NAME") + ".s3.amazonaws.com/lo.tar.gz -o /tmp/lo.tar.gz && cd /tmp && tar -xf /tmp/lo.tar.gz"
exec.Command("sh", "-c", cmd).Run()
os.Setenv("HOME", os.Getenv("LAMBDA_TASK_ROOT"))
cmd := "mkdir -p /tmp/cache/fontconfig && fc-cache " + path.Join(os.Getenv("HOME"), ".fonts")
exec.Command("sh", "-c", cmd).Run()
}

.fonts ディレクトリに使用するfontファイルをデプロイパッケージに含めてlambdaにアップロードします。

以下ディレクトリ構成でzipに固める

.
├── .fonts
│   ├── hoge.ttc
│   └── fuga.ttc
├── .fonts.conf
└── main

main goのbuild済みファイルです。
.fonts.confは参考URLのまま以下の設定にしています。

<fontconfig>
<cachedir>/tmp/cache/fontconfig</cachedir>
</fontconfig>

これで実行するとPDFに日本語が含まれていてもOKになりました!

Timezone

構築後に社内で試してもらっていると日付関係が変?という報告がありました。
LambdaのデフォルトtimezoneはUTCなのでExceleで =Now() とかするとUTCの時刻になるようです。
これは対応が簡単で、lambdaの環境変数設定で解決。
TZ をkeyにして設定可能でした。

key: TZ
value: Asia/Tokyo

ちなみに日付の並び順は変わらなかったので、Excel側でフォーマット指定してもらうことにしました…

=TEXT(NOW(),"yyyy/MM/dd hh:mm:ss")

これにて無事日本語が含まれるExcelデータもPDF変換可能です!

その他ハマったところ

バイナリメディアタイプ

要件のところで書いていた話

  • ExcelファイルをWEB APIにPOSTしたらPDF変換されて返ってくる
  • 今回はbase64エンコードしたデータをjsonでやり取りする方式(理由は後述)

API Gatewayの設定でバイナリメディアタイプを設定することで、ファイルアップロードしたらPDFがそのままダウンロードできる…と思ってやっていたのですが、どうにもうまくいかず断念しました。(これは自分のgo言語の力量不足かもしれませんが..)

あと、ブラウザからではリクエストヘッダーに Accept: application/pdf が付与されないためCloudFrontをかまさないといけない、、、ということなので今回はjsonでやり取りでいいかな、となったのも理由です。

送信するときにExcelデータをbase64エンコードして、変換したPDFもbase64エンコードして返却させています。
送信側でプログラム書く場合はこの方がシンプルに構築できるのでいいかな、と感じています。

API Gatewayのタイムアウト

API GatewayのタイムアウトはLambdaより短いです。
Lambda ・・・最長15分

よくある質問 – AWS Lambda |AWS

Q: AWS Lambda 関数はどれくらいの時間実行できますか?

AWS Lambda 関数は、1 回あたりの実行時間を最長 15 分に設定することができます。タイム> アウトは 1 秒から 15 分までの間で任意に設定できます。

API Gateway ・・・最長29秒

Amazon API Gateway の制限事項と重要な注意点 – Amazon API Gateway

統合のタイムアウト・・・Lambda、Lambda プロキシ、HTTP、HTTP プロキシ、AWS 統合など、すべての統合タイプで 50 ミリ秒~29 秒。

また制限の解除も対象外です。

Lambdaのデフォルトのメモリ(128MB)だとタイムアウトすることが多かったのでしたので256MBで実行するようにしました。

最後に

サーバレス開発で今回のように外部コマンドもLambda上で展開してしまえば結構なんでもできそうだなと思いました。
API gatewayのバイナリメディアタイプに関しては引き続き調べていきたいところです。

あと今回はサーバーレスアプリ自体にはあまり触れませんでしたが SAM CLI を使用して開発、デプロイしています。

GitHub – awslabs/serverless-application-model: AWS Serverless Application Model (SAM) is an open-source framework for building serverless applications

このあたりの話はまたの機会に…

以上です!

Amazon Elastic File System (Amazon EFS) におけるバーストとプロビジョニングの変更を自動化する

クラウド事業部の原口です。

AWSのEFSが発表されてから「これを待っていた!!」というエンジニアの方も多かったと思いますが、割と落とし穴的な制限が多く、
その中の一つとしてEFSの「バースト」と「プロビジョニング」の2つのスループットモードがあります。

バーストスループットモードとは何か。

誤解を恐れず簡単に説明しますと、スマフォの帯域制限みたいな機能となります。
EFSではバーストクレジットと呼ばれる値があり、このクレジットがある間は非常に高速なスループットでデータのやり取りが可能なのですが
クレジットがなくなると制限がかかりめちゃくちゃ低速なファイルストレージになってしまいます。
これはプロダクション環境ではサーバ障害とも言えるレベルでの低速さになります。

バーストクレジットを回復するには?

基本的にファイルストレージを使っていなければ回復していきます。
そのため、EFSの利用頻度がクレジットの回復力を下回る場合はクレジットは減る事がありません。

また、使っているEFS内のサイズによってもクレジットのそもそもの量が異なります。
大きいファイルが多数あるなど、EFS内のデータ量が多ければクレジットのそのものの絶対量が多く付与されます。

これを理解してない場合に起こる問題

基本的には運用時、EFSはバーストスループットモードとして動作する事がほとんどですが、
よくある事例としては「テスト段階では高速で快適に動いていたのに、本番運用していたら急激に低速になり障害があった」という事例です。

CloudWatchのメトリクスに「BurstCreditBalance」というメトリクスがありますが、こちらがバーストクレジットで、
ゼロになると低速なストレージになってしまいます。

f:id:cs_sonar:20190826124939p:plain

上記のようなグラフの場合は運用で徐々にクレジットが減っていってるのでずっと運用しているといつかは障害となりそうです。

プロビジョニングスループット

さて、バーストクレジットを使い果たして低速となってしまった場合はどうすればいいのでしょうか?

もちろん「大きなファイルを作ってバーストクレジットを増やす」というのも一つの解決策ですが、
一般的にはバーストモードからプロビジョニングスループットというモードへ変更する事で対策できます。

プロビジョニングスループットは帯域保証のような機能で50(MB/秒)といった値を設定する形でスループット値を保証させるモードです。
このモードではクレジットなどに関係なく設定した速度は必ず保証されます。

しかし・・・このプロビジョニングスループットは非常に高額です。
1MB/秒 の保証にあたり月額7.2USDもかかります。
件のように50MB/秒を保証させると月額360USD ≒ 38,000円。
ただでさえEFSは高額なのにこの価格は無視できる額ではありませんよね。

プロビジョニングとバーストクレジットを交互に設定をする事でコスト削減を図る

実はバーストクレジットからプロビジョニングスループットに変更すると
プロビジョニングスループット中はバーストクレジットが回復していきます。

f:id:cs_sonar:20190826125825p:plain

8/23の途中からプロビジョニングスループットとしていますが、3日ほどでクレジット最大値まで回復していますね。

つまり、最もコスト効率がいいのは

クレジットを限界まで使い直前でプロビジョニングスループットに変更 。
クレジットが回復したらバーストスループットに変更。

という形となります。

CloudWatch + SNS + lambda を使ったバーストとプロビジョニングの変更を自動化

ここからが本題。
上記の形がコスト効率が最もよいのですがこんなの人の手でぽちぽちやってれないので自動化します。

まずは、バーストとプロビジョニングの変更を行うlambda関数を作成します
バーストモードにするものとプロビジョニングにするものの2つ

EFS-to-bursting

console.log('Loading function');
var https = require('https');
var http = require('http');
const aws = require('aws-sdk');
var efs = new aws.EFS();
exports.handler = (event, context) => {
(event.Records || []).forEach(function (rec) {
if (rec.Sns) {
var message = JSON.parse(rec.Sns.Message);
var params = {
FileSystemId: message.AlarmDescription, /* required */
ThroughputMode: 'bursting'
};
efs.updateFileSystem(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else     console.log(data);           // successful response
});
// ここでslack等に通知するといいと思う
}
});
};

EFS-to-provisioned-iops

console.log('Loading function');
var https = require('https');
var http = require('http');
const aws = require('aws-sdk');
var efs = new aws.EFS();
exports.handler = (event, context) => {
(event.Records || []).forEach(function (rec) {
if (rec.Sns) {
var message = JSON.parse(rec.Sns.Message);
var params = {
FileSystemId: message.AlarmDescription, /* required */
ProvisionedThroughputInMibps: '10', // プロビジョニング10MB
ThroughputMode: 'provisioned'
};
efs.updateFileSystem(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else     console.log(data);           // successful response
});
// ここでslack等に通知するといいと思う
}
});
};

FileSystemId: message.AlarmDescriptionの部分ですが
受け取ったCloudWatchアラームにはどのEFSがアラームを出したか、というものが取得しずらかったので
CloudWatchアラームのDescriptionにEFSのIDを載せる形にしました

SNS(Simple Notification Service)のトピックを作成してlambdaに連携します

SNSは通知先にメールやhttpなどだけでなくlambdaに通知できます。
ですので、バーストモードにするlambdaに連携するトピックと、
プロビジョンドにするlambdaに連携するトピックの2つを作成します。

f:id:cs_sonar:20190826131112p:plain

cloudwatchにてアラームを作成

BurstCreditBalanceを監視し、上記作成のSNSに通知するアラームを作成します。
こちらも、
・バーストクレジットが一定数以上の時 -> プロビジョンモードにするSNSに発火
・バーストクレジットが一定数以下の時 -> バーストモードにするSNSに発火
という形で2つ作成します。

f:id:cs_sonar:20190826131630p:plain

lambdaの所でも書いた通り、DescriptionにはEFSのIDを入力します

f:id:cs_sonar:20190826131750p:plain

バーストとプロビジョニングの変更を自動化できました

このように複数EFSあると便利さがわかりますね!

f:id:cs_sonar:20190826132003p:plain

クレジットが減るとプロビジョニングになり、クレジットが充足するとバーストモードになる形です

注意点

EFSの制限で、スループットモードの変更は1日に1回までしかできません!
そのため、1日でバーストクレジットを使い果たしてしまうような環境ではずっとプロビジョニングモードで動かす必要があります。

本日は以上で!

AWS Cloud9 を使った開発環境構築

こんにちは。
エンジニアの西山です。

今回、初めてクリエイターズブログを書かせていただきます。

ところで、プログラミングを学び始めた人が最初につまずくのは
ifでもforのループでもなく開発環境の構築だと考えています。

プログラムを動かして楽しみたいと思っても
まずは動かすことが出来る環境を作らなければ始まりません。

XAMPP? Docker?
様々な選択肢があるとは思いますが
クラウドサーバーを使ってみる機会にもなると思いますし
AWS Cloud9を使った方法を紹介いたします。

私は以前、AWSに組み込まれる前のCloud9のサービスで
練習で関数の使い方等、簡単なプログラムを書いていました。

AWSに組み込まれてからは使ったことがなかったので、挑戦してみます。

プログラマもサーバーの事を知らなければいけない時代
私自身もAWSの使い方をもっと使って学んでいかなければ・・・。

AWSのアカウント作成は下記のページで詳細に説明しています。
https://aws.amazon.com/jp/register-flow/
※1年間の無料枠を使うことができます(使いすぎると費用も掛かってくるので気をつけてください)

アカウントが作成できましたら
AWS マネジメントコンソールへログインします。

早速Cloud9の開発環境作るぞ、と思いましたが
下記のページを見ると
https://docs.aws.amazon.com/ja_jp/cloud9/latest/user-guide/setup-express.html

AWS アカウントのルートユーザーとして AWS Cloud9 を使用することは可能ですが、
これは AWS セキュリティのベストプラクティスではありません。
代わりに IAM ユーザーとして AWS Cloud9 を使用することをお勧めします。

rootは神 というのはサーバーで最初に学んだ事なので、何でもかんでもやらせたらよくないです。
神が乗っ取られたら破壊神になるし。

まずはチームとユーザーの追加を行なっていきます

AWS Cloud9 のチームセットアップ
「ステップ 2.1: コンソールで IAM グループを作成する」
https://docs.aws.amazon.com/ja_jp/cloud9/latest/user-guide/setup.html#setup-create-iam-resources-group-console

  下記の動画も参考になります。
  https://www.youtube.com/watch?v=XMi5fXL2Hes

グループ「Cloud9Group」というのを作成しました。

その後は「ステップ 2.2: IAM ユーザーを作成して、コンソールでグループにユーザーを追加する」
https://docs.aws.amazon.com/ja_jp/cloud9/latest/user-guide/setup.html#setup-create-iam-resources-user-console

ユーザー作成が完了したページで、下記の記載があるCSVがダウンロードできますので必ずダウンロードしてください。

  • アクセスキー ID
  • シークレットアクセスキー
  • パスワード
  • コンソールへのログインURL

ユーザー「Cloud9User」というのを作成しました。

そして「ステップ 3: グループに AWS Cloud9 アクセス権限を追加する」
https://docs.aws.amazon.com/ja_jp/cloud9/latest/user-guide/setup.html#setup-give-user-access

権限をつけて使えるようにしたので
「ステップ 4: AWS Cloud9 コンソールへのサインイン」
https://docs.aws.amazon.com/ja_jp/cloud9/latest/user-guide/setup.html#setup-sign-in-ide

改めて作ったアカウントでサインインし直します。
https://console.aws.amazon.com/cloud9/

さあ、いよいよAWS Cloud9を使い始めます。

まだリージョンに東京がないので
アジアパシフィック (シンガポール)を選択しました。

f:id:seeds-std:20181211204323p:plain

「Create environment」ボタンを押して

f:id:seeds-std:20181211204355p:plain

「Name」に作業環境の名前をつけます「practice」とつけました。
「Next step」ボタンをクリック

f:id:seeds-std:20181211204415p:plain

「Environment type」は「Create a new instance for environment (EC2)」を選択

費用を抑える為、
「Instance type」は「t2.micro」
「Cost-saving setting」は「After 30 minutes」にします。

※Cloud9 が接続する新しい Amazon EC2 インスタンスを作成します
 IDE を開いたときに自動的に起動します。
 インスタンスは IDE を終了して 30 分後に停止する設定になります

「Next step」ボタンをクリックし
確認画面で「Create environment」をクリックして環境を作成

f:id:seeds-std:20181211204440p:plain

数分待てば環境が出来るので待ちます。

f:id:seeds-std:20181211204453p:plain

左側の「README.md」などのファイルが表示されているエリアを右クリックして
「New File」
index.php という名前で動作確認のファイルを作成します。

文字表示だけでなく、きちんと計算も出来ているか見る為
計算式も入れています。

f:id:seeds-std:20181211204509p:plain

グロナビ「run」をまず押してWebサーバーが動く状態にします。

f:id:seeds-std:20181211204620p:plain

その後で、グロナビ「Preview」
「Preview Running Application」をクリックして結果表示します。

文字列と計算結果が表示されていますのでOK

料金は実際に使用した分に対してのみ発生し、最低使用料金や前払費用はありません。(無料利用枠内でしたら無料)

無料枠を超えた時の使用例

t2.micro Linux インスタンス で
デフォルトの設定 (30分間の自動休止状態を設定して
1か月に 20日間、1日 4時間 IDE を実行) を使用する場合
90時間の使用となった時

月額料金 1.85 USD 約200円!

AWS Cloud9 料金
https://aws.amazon.com/jp/cloud9/pricing/

ビール1本控えて、クラウドサーバー触れって事ですね。

AWS Cloud9には環境を他の人と共有する機能もあるようで
チームや指導で使う時にも便利な機能かと思われます。

まずはプログラムもサーバーも、色々動かして触ってみるのが重要だと思うので
手を動かす事を意識して頑張ります。

打つべし。打つべし。(課金には気をつけて)

以上、西山でした。

AWS IoT Enterprise Buttonを使ってSlackに通知を送ってみた

はじめまして!18年度新卒・WEBエンジニアの石田です。

弊社のコーヒーメーカーがリニューアルされ、コーヒーを淹れる機会がすごく多くなりました。
しかし、コーヒーメーカーができてから30分で保温が切れる設定で、
・追加で保温ができない…
・どうせならできたてが飲みたいけど、できたタイミングが分かりにくい…
・かといっていちいちSlackにコーヒーできましたって書くのもめんどくさい!
→自動投稿させたい!
→→ボタンワンクリックで済ませたい!

ということで、会社にAmazon「AWS IoT Enterprise Button」を買ってもらい、折角なのでクリエイターズブログに投稿することにしました!

f:id:seeds-std:20181105211732j:plain
新コーヒーメーカーと「AWS IoT Enterorise Button」

新コーヒーメーカーと「AWS IoT Enterorise Button」

AWS IoT Enterorise Button

ワンプッシュで商品が注文できる!と話題になった Amazon Dash Buttonをベースにした、自由にプログラムを動作させることのできるボタンです。
ボタンのクリックをトリガーに、AWSのサービスに接続できます。すごい。

f:id:seeds-std:20181105212349j:plain
ちっちゃいです

モバイルアプリを使ってWiFiの設定ができ、ステータスも確認可能。

f:id:seeds-std:20181105213458p:plainf:id:seeds-std:20181105221850p:plain
iOSアプリ「AWS IoT 1-Click」

何度か設定してみたのですが、デバイスリージョンはオレゴン一択なのでしょうか。東京リージョンに変更できませんでした。

そこからAWS IoT 1-Clickというサービスを選択し、プロジェクトを作成することでデバイスの動作を定義することができます。

選択できるのは
・SMSの送信
・Eメールの送信
・Lambda関数の選択
Slackに自動投稿したかったのでLambda一択でしたが、SMS・Eメール送信も色々使えそうで良いですね。

あとはLambdaでjavascriptを使ってwebhookにPOSTすれば投稿完了!

exports.handler = function(event, context) {
const slack = (params) => {
let https = require('https');
let host = 'hooks.slack.com';
let data = JSON.stringify({"text": params['message']});
let options = {
hostname: host,
port: 443,
path: params['path'],
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data)
}
};
let req = https.request(options, (res) => {
console.log('status code : ' + res.statusCode);
res.setEncoding('utf8');
res.on('data', (d) => {
console.log(d);
});
});
req.on('error', (e) => {
console.error(e)
;});
req.write(data);
req.end();
};
var post = function() {
return slack({
path: 'hogehogehogehogehoge',
message: 'コーヒーができました:coffee:'
});
};
setTimeout(post, (60 * 6 * 1000));
};

f:id:seeds-std:20181105221032p:plain
アイコンはデザイナー・コウノ氏が作ってくれました!かわいい

6分で完成するので、その分だけタイムアウトさせるようにしています。
起動時間は5分までだと思ってたからどうしようかな…と思っていたのですが、15分までいけるようになっていたのが驚きでした。

IoT ButtonやLambdaを触ったのははじめてだったので時間がかかりましたが、セットアップからSlackに投稿できるようになるまで1時間ほど。すごく簡単でした。
他にもいろいろな使い方ができそうですね。シーズでは早速、更に2台のIoT Buttonを追加購入しました(笑)
社内でアイデアを募ってどういう使い方をしようかと思案中です!

弊社コーポレートサイトリニューアルとその裏側

お久しぶりとなってしまいました。原口です。

10/1に弊社コーポレートサイトがリニューアルされました。

www.seeds-std.co.jp

今回、社長より「技術的な部分は自由にやっていいよ」という事でしたので自由にやらせていただきましたので、
このサイトにおける技術的な部分にフォーカスして紹介をしていきたいと思います。

システムコンセプト

通常コーポレートサイトといいますとwordpressなどのCMSの導入をする事がほとんどです。
弊社でもこれまではwordpressを利用していましたが運用するうちに気づいた事として管理が非常に面倒という点がありました。
サイトの更新よりもwordpressのアップグレード回数の方が多かった・・・となる月もザラにあり、
更新の簡単さと差し引いても目に見えにくい人的コストが高い状態は否めませんでした。

そのため、リニューアルにあたっては早いうちから、管理コストの少ない構成とする事は決めていました。
できる限り静的ファイルのみでの公開をし、お問い合わせフォームなどの、どうしてもプログラムの必要な部分は
サーバーレスアーキテクチャを採用するという方針を決定致しました。

バックエンド

インフラ構成は以下の通りです

f:id:cs_sonar:20181009211159p:plain

サイトの大部分はs3で公開されています。
デプロイは開発者のローカル環境より

npm run prod

といったビルドコマンドでs3に同期するような仕組みです。

大部分はs3で公開されている為、s3は静的ホスティングとしています。
前段にcloudfrontをかませていますが、これは独自ドメインにてSSLを使いたかったが為に使用しています。
そのためキャッシュ及びネガティブキャッシュのTTLは0です。
SSLはACMで取得しました。

唯一お問い合わせフォームがシステムに絡む部分となりますので
こちらはAPI gatewayとlambdaを利用しています。
この部分は一括して、リードプログラマの川勝さんに担当していただけました。

lambdaはvalidateと、SMTP-AUTHにて別のサーバーにメールリレーするだけの簡単なプログラムとなっています。
サーバレスといいつつ、唯一メールサーバーのみ弊社データセンタを利用していますが、
こちらも本来はAWS SESを利用しても問題はないかと思います。

この構成で約10日間ほど運用していますが現在で総額 $1.5ほどの費用となります。
TOPページに動画を使っている関係かこのうちの$1.2は転送量(cloudfront)です。
このままでおおよそ$5/monthほどのコストで運用できるのではないでしょうか。

この低コストに加えて、サーバー機の保守やOS、ミドルウェア、アプリケーションの脆弱性など、
まったく考えなくてもよいという見えない人的コストも大きく削減されています。

フロントエンド

フロントエンド側では弊社フロントチームに新卒で参加した寺澤さんがコーディングから実装までほぼすべてを行いました。
モバイルファーストを考え、スマフォの際はできるだけ転送量がかからないようjsやcssをminifyを行ったり、モバイル端末の際はTOP動画をgifアニメにするなどの変更を行っています。PC側の動画の圧縮なども作業いただきました。

静的ファイルを生成するという構成上、テンプレートエンジンの利用は当初より考えていてPugを使用しています。
ビルドはgulpを利用していて、ビルド時のコマンドで生成したhtml、css、画像、jsをs3へ持っていく形としています。

jsフレームワークはVue.jsです。スライダーやページの動きはこちらで実装し、一部ページ(newsや制作実績、お問い合わせページ)ではSPAっぽいもので構成しています。

1点、起こった問題として、s3静的サイトホスティングではmod_rewriteのようにURLの書き換えができない点がありました。
リダイレクトは可能なのですがリライトはできないのですね・・・。
前述の通り一部ページはSPAでの実装を行っていたのですが、
s3での運用に際し、この点はURLが少し不格好となってしまいますがクエリストリングで対応するようにしました。

デザイン

昨年、弊社に入社されたデザイナーの河野さんが全て取り仕切って行ってくれました。
モバイルファーストと呼ばれるこの時代・・・レスポンシブデザインをベースに
スマフォでいかに見やすいか、という点を注意してデザインいただけました。

TOPにて背景動画を流したり、遊び心のあるイラスト(河野さんの自作!)など、素敵なデザインになったと思います。
イラストの一部にシーズ社員数名が紛れ込んでいるので、是非探してみてください。

終わりに

今回の構成では今後できるだけ手間をかけない、可能な限り今後の運用が低コストとなるよう考えて制作しました。
通常CMSと考えるとwordpressやサーバー、そしてデータベースの準備などなど、、、様々な事がデファクトスタンダードとして存在しますが、
規模に対して過剰な設備であったり、本来注視すべきコンテンツ以上にこれらの対応で時間が取られる事もしばしばあります。
本当にそこまで必要なのか?本当にそれらを準備しないと目的が達成できないのか?
という観点は通常のシステム開発でもとても重要なものであると改めて思いました。

同様の事例でお困りの事があれば是非、ご相談下さい!

社内WindowsサーバーをAWSに移行する話1

経緯

シーズでは、見積書・請求書の発行などに社内にWindowsサーバーを立て弥生販売を使っていました。

複数拠点から複数人が同時に使うためネットワーク版5ライセンスです。

社内にサーバーを置くメリットとして、

*ギガビットLANでの高速アクセス

*社外との通信が発生しないためセキュリティーが高い

*売るほどサーバーラックがあるのでコストはあまり気にならない

といった利点がありましたが
サーバー筐体費用、Windowsサーバー、SQLライセンスなどの初期費用(トータル約40万※構築費用は自前のため無料)がかかるうえ
毎年のライセンス更新や筐体保守費用、また毎月の電気代・空調費用も馬鹿になりません。
さらに、場所も取るし、空調にも気を使うし、なにかとメンテも大変なのでクラウドに行きたいと考えておりました。

要件

*AWSで24時間365日稼働(ただし夜間は止めるかも)

*OSはWindowsサーバー2012

*弥生販売ネットワーク版5ライセンスを動かす

*シーズ社内とVPNでセキュアに常時接続

特に最後のVPNで社内とシームレスかつセキュアに接続が一番重要ですね。

これらの要件をAWSでこのように準備しました。

AWS構成

*ec2インスタンス(Windows2012) t2.small $36.60

*EBS45GB $5.40

*VPC VPN接続 $36

合計 約78ドル=約8,424円 (1ドル108円)

f:id:panmizser:20161121214003p:plain

EBSボリューム、転送量、EIP、IOなど少額の従量制がありますが、それらを含んだとして、約8,500円です。

移行してどうだったか

結論としては、最高でした。

まず費用としては

サーバー1台をデータセンターでハウジング(月50,000相当,OSライセンス諸々込)として、それが月8,500円ポッキリになりました。(83%DOWNです!)

また弥生を使わない夜間止めることで更にコストダウンも可能です。

次に、回線速度やアプリの使用感ですが、こちらも全く問題ありません。

インスタンスタイプは最初 large → medium と様子を見ていきましたが最終的にはsmallまで落としても操作感に問題ありませんでした。

(ただし弥生インストール時はlargeでやったほうが効率がいいです)

回線速度は、AWSの提供するVPNサービスを利用することで全くストレスを感じません。
(以前、WindowsインスタンスからPPTPで会社ルーターに繋げたりしたことがありましたが、その場合、一応つながりますが、結構遅かったです。)

次回は、これらAWSの設定、社内VPNルーターの設定を含め詳しく解説していきたいと思います。

AWSとVPN接続を張ったけど転送量はどうなの?

blog_img_4285

先日、CTO原口くんにお願いして、シーズ社内にあったWindowsサーバーをAWSに移行しました。
AWSにWindowsServerOSインスタンスを立てて、シーズからはVPN(IPsec接続)でシームレスかつセキュアに社内ネットワークを拡張したイメージです。
その話はまた別にするとして、その時、「AWSでファイルサーバーをやるのってどうなの?使えるの?」という話になったので検証をちょこっとやってみました。

ファイルサーバー自体は、EC2.linuxでsambaを立てるなり、WindowsServerでファイルサーバーをするなりして簡単に実装できますが、一番のネックは転送スピードということになりますよね。

そこを検証してみました。

環境

社内LAN環境はすべてギガビット回線
=>社内ファイルサーバーにはギガビット転送ができる(1000Mbps=125MB/s)

インターネットともギガビットの専用線を引いている。
インターネット接続はRTX1200を利用している。VPNルーターとしても利用している。
AWS VPC VPN接続にはRTX1200からIPSECで接続している。

社内、社外インターネットともギガビットのため、理論上はすべてギガで通信できる環境です。

社内ファイルサーバーの転送スピード

アップロード
inG

アップロード(パソコンから社内ファイルサーバー)スピードです。
だいたい平均的に32.5MB/s(260Mbps)
となっているので、理論値の26%ぐらいしかでていませんが、通常利用ではまったく問題のない速さだと思います。

ダウンロード
outG

ダウンロード(社内ファイルサーバーからパソコン)はさらに早くなりました。
57.2MB/s(457.6Mbps)!
これ以上求める必要はないでしょう。というレベルだと思います。

AWS上VPN接続

アップロード
in

アップロード(パソコンからAWS-VPNファイルサーバー)転送スピードは6.82MB/s(54Mbps)です。
スピードはかなり落ちましたが、実用性ではどうでしょうかね。
一昔前の100Mbps環境時代の速度というところでしょうか。
今のギガに慣れているとストレスかもしれませんが、使えなくはなさそうですね。

ダウンロード
out

ダウンロード(AWS-VPNファイルサーバーからパソコン)は逆に遅くなりました。
3.68MB/s(29.44Mbps)で止まっています。
出だしは早かったのですが、なにか制限が入っているのでしょうかね。
とはいえ、まあ実用的に全く使えないわけではなさそうです。

結論

AWS-VPNは100Mbpsで繋がっていた時代の速度ではあるが、使えないことはなさそう。
しかし、転送量でも課金されるので注意が必要。(アップロードは無料)
1000GB = $139.86
100GB = $13.86
10GB = $1.26

Terraformを使ってみました

blog_img_4238

Terraformは、あらかじめインフラ構成を設定ファイルに記述して、
クラウド環境に適用・管理するツールです。
Vagrantなどを開発しているHashiCorpのツールになります。

AWSだけではなく様々なプロバイダに対応していますが、AWSで使用してみました。

インストール

Linuxサーバに入れてみました。
ダウンロードしてきて展開するだけで使えます。

cd /opt
mkdir terraform
cd terraform
wget https://releases.hashicorp.com/terraform/0.6.16/terraform_0.6.16_linux_amd64.zip
unzip terraform_0.6.16_linux_amd64.zip

パスを通すと便利でした。

vi ~/.bashrc

export PATH=/opt/terraform:$PATH

AWS側事前準備

  • AWSアカウント作成
  • terraformで触るリソースをいじれるポリシーを付与したIAMユーザを作成

terraformの動作イメージ

必ず使うコマンドは以下の三つ

terraform plan
dry-runです。設定ファイルの記述ミスなどをチェックしてくれます

terraform apply
実行です。設定ファイル通りの構成を実装してくれます

terraform destroy
作成した構成を削除してくれます

terraform初期設定

まずはAWSアクセスキーなどの設定。
terraformは「terraform.tfvars」というファイルがカレントディレクトリにあると
そちら中を読んで変数として取り扱ってくれます。
注意点として、このファイルでは配列は設定できないようです。

このようにアクセスキーなどを変数として設定しておく事ができます。

file : terraform.tfvars

#####################################
#Variable Settings
#####################################
#AWS Settings
access_key = "xxxxxxxxxxxxxxxxxxx"
secret_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
region = "ap-northeast-1"

このファイルは.gitignoreとかでリポジトリでは管理しないようにします。

次のこの変数をtfファイル内で使用できるようにvariableで定義します。
上記のterraform.tfvarsの記述内容は環境変数に入るので、それを定義します。

file : variables.tf

#####################################
# Variable Settings
#####################################
#AWS Settings
variable "access_key" {}
variable "secret_key" {}
variable "region" {}

上記で設定した変数を使ってterraformのAWS設定を記述します

file : aws.tf

provider "aws" {
access_key = "${var.access_key}"
secret_key = "${var.secret_key}"
region = "${var.region}"
}

以上で初期設定は終わり。
terraform planやterraform applyをするとカレントにある *.tf ファイルをすべて読み込んでくれます。
また、一度applyを実行すると、terraform.tfstateというファイルが作成されます。
terraform.tfstate は、Terraform が管理しているリソースの状態が保存されているのだと思います。
おそらく次回実行時にこちらのstateファイルとの差分でどのようなAPIを投げるか決定しているのだと思います。

VPCを作成してみる

実際にいろいろ設定をしてみたので紹介です。
以下は

・VPCの作成
・サブネット二つ作成(いずれもパグリックサブネット)
・DHCPオプションの作成
・インターネットゲートウェイ作成
・ルーティングテーブル作成

を行っています。

file aws_vpc.tf

#####################################
# VPC Settings
#####################################
resource "aws_vpc" "vpc_main" {
cidr_block = "${var.root_segment}"
enable_dns_support = true
enable_dns_hostnames = true
tags {
Name = "${var.app_name}"
}
}
#####################################
# DHCP option sets
#####################################
resource "aws_vpc_dhcp_options" "vpc_main-dhcp" {
domain_name = "${var.dhcp_option_domain_name}"
domain_name_servers = ["AmazonProvidedDNS"]
tags {
Name = "${var.app_name} DHCP"
}
}
resource "aws_vpc_dhcp_options_association" "vpc_main-dhcp-association" {
vpc_id = "${aws_vpc.vpc_main.id}"
dhcp_options_id = "${aws_vpc_dhcp_options.vpc_main-dhcp.id}"
}
#####################################
# Internet Gateway Settings
#####################################
resource "aws_internet_gateway" "vpc_main-igw" {
vpc_id = "${aws_vpc.vpc_main.id}"
tags {
Name = "${var.app_name} igw"
}
}
#####################################
# Public Subnets Settings
#####################################
resource "aws_subnet" "vpc_main-public-subnet1" {
vpc_id = "${aws_vpc.vpc_main.id}"
cidr_block = "${var.public_segment1}"
availability_zone = "${var.public_segment1_az}"
tags {
Name = "${var.app_name} public-subnet1"
}
}
resource "aws_subnet" "vpc_main-public-subnet2" {
vpc_id = "${aws_vpc.vpc_main.id}"
cidr_block = "${var.public_segment2}"
availability_zone = "${var.public_segment2_az}"
tags {
Name = "${var.app_name} public-subnet2"
}
}
#####################################
# Routes Table Settings
#####################################
resource "aws_route_table" "vpc_main-public-rt" {
vpc_id = "${aws_vpc.vpc_main.id}"
route {
cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.vpc_main-igw.id}"
}
tags {
Name = "${var.app_name} public-rt"
}
}
resource "aws_route_table_association" "vpc_main-rta1" {
subnet_id = "${aws_subnet.vpc_main-public-subnet1.id}"
route_table_id = "${aws_route_table.vpc_main-public-rt.id}"
}
resource "aws_route_table_association" "vpc_main-rta2" {
subnet_id = "${aws_subnet.vpc_main-public-subnet2.id}"
route_table_id = "${aws_route_table.vpc_main-public-rt.id}"
}

この例の通り、作成したリソースのIDなどは他のリソースで読み込む事が可能です。
一番上で作成したVPCのIDは以下のように取得できます。

${aws_vpc.vpc_main.id}

セキュリティグループを作成してみる

特筆するところはないですが、ソース元をセキュリティグループにする例も載せてみました。
ソース元を配列にする事で複数の設定ができます。
terraform.tfvarsに配列が指定できたらいいんですが・・・

resource "aws_security_group" "lb_sg" {
name = "${var.app_name} Load Balancer"
vpc_id = "${aws_vpc.vpc_main.id}"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
description = "${var.app_name} Load Balancer"
}
resource "aws_security_group" "web_sg" {
name = "${var.app_name} WEB Server"
vpc_id = "${aws_vpc.vpc_main.id}"
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["${var.root_segment}"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["${var.root_segment}"]
security_groups = ["${aws_security_group.lb_sg.id}"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
description = "${var.app_name} WEB Server"
}

RDSの作成

RDS用のサブネットグループを作成する部分が少しはまりました

resource "aws_db_instance" "default" {
allocated_storage       = 5
identifier              = "${var.rds_name}"
engine                  = "${var.rds_engine}"
engine_version          = "${var.rds_engine_version}"
instance_class          = "${var.rds_instane_type}"
name                    = "${var.rds_db_name}"
username                = "${var.rds_user}"
password                = "${var.rds_pass}"
db_subnet_group_name    = "${aws_db_subnet_group.default.id}"
parameter_group_name    = "${var.rds_parameter_group}"
vpc_security_group_ids  = ["${aws_security_group.mysql_sg.id}"]
backup_retention_period = "1"
backup_window           = "17:08-17:38"
storage_type            = "gp2"
maintenance_window      = "Sat:13:38-Sat:14:08"
multi_az                = false
apply_immediately       = true
publicly_accessible     = true
}
resource "aws_db_subnet_group" "default" {
name = "default-${aws_vpc.vpc_main.id}"
description = "Our main group of subnets"
subnet_ids = ["${aws_subnet.vpc_main-public-subnet1.id}","${aws_subnet.vpc_main-public-subnet2.id}"]
}

s3の作成

ポリシーを設定する場合は外部に用意したポリシーのjsonを読み込ませて設定してみました。

resource "aws_s3_bucket" "b" {
bucket = "${var.s3_bucket_name}"
acl = "private"
policy = "${file("s3_bucket_policy.json")}"
}

elbの作成

# Create a new load balancer
resource "aws_elb" "default" {
name = "${var.elb_name}"
security_groups = ["${aws_security_group.lb_sg.id}"]
subnets = ["${aws_subnet.vpc_main-public-subnet1.id}","${aws_subnet.vpc_main-public-subnet2.id}"]
listener {
instance_port = 80
instance_protocol = "http"
lb_port = 80
lb_protocol = "http"
}
health_check {
healthy_threshold = 2
unhealthy_threshold = 2
timeout = 5
target = "HTTP:80/index.html"
interval = 30
}
}

dynamoDBのテーブル作成

range_keyがソートキーになります

resource "aws_dynamodb_table" "users" {
name = "${var.dynamodb_prefix_name}.users"
read_capacity = 1
write_capacity = 1
hash_key = "id"
range_key = "username"
attribute {
name = "id"
type = "N"
}
attribute {
name = "username"
type = "S"
}
}

Nが数値、Sが文字列、Bがバイナリです。

elasticacheの作成

redisを作成してみた

resource "aws_elasticache_cluster" "redis" {
cluster_id = "${var.cache_cluster_name}"
engine = "redis"
node_type = "${var.cache_instane_type}"
port = 6379
num_cache_nodes = 1
parameter_group_name = "${var.cache_parameter_group}"
security_group_ids = ["${aws_security_group.redis_sg.id}"]
subnet_group_name = "${aws_elasticache_subnet_group.subnet.id}"
}
resource "aws_elasticache_subnet_group" "subnet" {
name = "${var.cache_cluster_name}"
description = "${var.cache_cluster_name}"
subnet_ids = ["${aws_subnet.vpc_main-public-subnet1.id}","${aws_subnet.vpc_main-public-subnet2.id}"]
}

IAMロールの作成

いろいろ躓きました・・・。
IAMロールの作成の場合、

  • ロールの作成
  • 管理ポリシーをアタッチ
  • インラインポリシーをアタッチ

という形なのですが、terraformではポリシーのリソース作成でIMAロールを指定します。
また、IAMロール作成時、「信頼されたエンティティ」のID プロバイダーとして
amazonを追加しないと使えないところがはまりました。(普段意識していなかったので・・・)

IAMロールの作成。
信頼されたエンティティにec2.amazonaws.comを設定しています。

resource "aws_iam_role" "sample_role" {
name = "${var.sample_iam_role_name}"
assume_role_policy = &lt;&lt;EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}

作成したロールに管理ポリシーをアタッチ
管理ポリシーの場合はポリシーarnを直接記述します。

resource "aws_iam_policy_attachment" "dynamodb" {
name = "AmazonDynamoDBFullAccess"
roles = ["${aws_iam_role.sample_role.id}"]
policy_arn = "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess"
}

作成したロールにインラインポリシーをアタッチ
インラインポリシーの場合はpolicyのjsonを直接記述します

resource "aws_iam_role_policy" "sqs_policy" {
name = "AmazonSQSFullAccess"
role = "${aws_iam_role.sample_role.id}"
policy = &lt;&lt;EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"sqs:*"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
EOF
}

EC2インスタンスの作成

作成したIAMロールを関連付けたインスタンスを作成します。
ついでに固定IPもつけてみました。
インスタンスに関連づける場合は「aws_iam_instance_profile」でまず
インスタンスプロフィールを作成する必要があるようです。

こちらのリソースで作成したnameの値をaws_instanceのiam_instance_profileで指定すれば
うまくいきました。

resource "aws_iam_instance_profile" "sample_role" {
name = "sample_role"
roles = ["${aws_iam_role.sample_role.name}"]
}
resource "aws_instance" "sample" {
ami = "${var.sample_ami}"
instance_type = "t2.micro"
vpc_security_group_ids = ["${aws_security_group.sample_sg.id}"]
subnet_id = "${aws_subnet.vpc_main-public-subnet1.id}"
iam_instance_profile = "sample_role"
associate_public_ip_address = true
key_name = "${var.key_pair_name}"
tags {
Name = "${var.sample_tag_name}"
}
root_block_device {
delete_on_termination = "true"
}
}
# 固定IPもつけてみる
resource "aws_eip" "lb" {
instance = "${aws_instance.sample.id}"
vpc      = true
}

AutoScalingの設定

こちらはあまりうまくいってないので使ってないですが、うまくいかなかったりうまくいったりします。

resource "aws_launch_configuration" "as_conf" {
name_prefix = "${var.launch_config_name}"
image_id = "${var.sample_ami}"
instance_type = "t2.micro"
iam_instance_profile = "api_role"
security_groups = ["${aws_security_group.sample_sg.id}"]
key_name = "${var.key_pair_name}"
associate_public_ip_address = true
lifecycle {
create_before_destroy = true
}
root_block_device {
volume_type = "gp2"
volume_size = "24"
delete_on_termination = true
}
}
resource "aws_autoscaling_group" "sample" {
name = "${var.auto_scaling_group_name}"
vpc_zone_identifier = ["${aws_subnet.vpc_main-public-subnet1.id}","${aws_subnet.vpc_main-public-subnet2.id}"]
launch_configuration = "${aws_launch_configuration.as_conf.name}"
max_size = 0
min_size = 0
desired_capacity = 0
default_cooldown = 300
health_check_grace_period = 300
health_check_type = "EC2"
load_balancers = ["${aws_elb.default.name}"]
force_delete = true
tag {
key = "Name"
value = "${var.api_tag_name}"
propagate_at_launch = true
}
}

root_block_deviceやelb_block_deviceを使うと以下のようなエラーが出てしまう為、現在はあまり使用していません。

Error refreshing state: 1 error(s) occurred:
* aws_launch_configuration.as_conf: Invalid address to set: []string{"root_block_device", "0", "encrypted"}

こちら、お分かりにな方がいたらご教授頂きたいです・・・

怖いところ

「割と軽微な修正」だと思って修正すると
インスタンスが削除されたりする可能性があります。

例えばインスタンスのIAMロールを変更はインスタンスの立ち上げ時にしか変更できないのですが
terraformでIAMロールを変更してterraform applyすると、インスタンスは削除されて新規作成される、、
という動作となります。

これはどう考えてもterraformが正しい動作をしているのですが
本番環境などでterraform applyを実行するのは怖すぎるな・・・と思いました。

個人的なTerraformの使いどころ

おなじような機能にAWS公式サービスのCloudFormationがありますが、
構成を管理してしまうところが嫌だと感じていました。

つまり初回構築だけ実行したい、、、という事ができないわけです。
もちろんTerraformにも構成管理をする機能はありますが
プロバイダ非依存である為、このあたりは柔軟に対応できます。

あと設定ファイルがjsonではないのでコメントなども書けるのもいいですね。
InteliJにはterraform用のプラグインもありますので
設定ファイル作成もしやすいです。

というわけで個人的には
「初期構築用ツール」、もしくは「開発環境構築ツール」として利用していこうと思います。
destroyでサクッと全部消えるのはとても便利

逆に構成を管理させたい場合は
WEB GUIにて変数をフォームで渡して実行、、って事ができるので
やはりCloudFormationの方がいいかなぁと感じています。
(AutoScaleはCloudFormationを使う事が多いです)

Page 2 of 3

© SEEDS Co.,Ltd.