:beginner: Amazon EC2 Simple Systems Manager (SSM) エージェントのインストール

:beginner:
Amazon EC2 Systems Managerを使用するためのエージェントを導入するまでの手順です。
内容としては初心者向けになっています。

前提条件

SSMエージェントの導入対象インスタンスからS3へのアクセス(HTTPS)が出来る必要があります。

Simple Systems Manager 用の管理ポリシーを追加

SSMを使用したいインスタンスに割り当てているIAMロールへSSMの管理ポリシーをアタッチする。

ロールがない場合は作成する。
2017-06-21-14-20-20.png

「AmazonEC2RoleforSSM」を選択し「ポリシーのアタッチ」を実施する。
2017-06-21-14-23-37.png

2017-06-21-14-26-28.png

Simple Systems Manager エージェントのインストール

:warning: Amazon Linux、RHEL、および CentOS 64 ビット の 東京リージョンの場合の手順

新規インスタンスの場合はユーザーデーターに以下を追加で定義する。

#!/bin/bash
cd /tmp
sudo yum install -y https://amazon-ssm-ap-northeast-1.s3.amazonaws.com/latest/linux_amd64/amazon-ssm-agent.rpm

既に稼働中のインスタンスの場合はログイン後に以下のコマンドを実行する。

sudo yum install -y https://amazon-ssm-ap-northeast-1.s3.amazonaws.com/latest/linux_amd64/amazon-ssm-agent.rpm
[ec2-user@ip-10-0-2-141 ~]$ sudo yum install -y https://amazon-ssm-ap-northeast-1.s3.amazonaws.com/latest/linux_amd64/amazon-ssm-agent.rpm
Loaded plugins: priorities, update-motd, upgrade-helper
amazon-ssm-agent.rpm                                            | 6.0 MB  00:00:00
Examining /var/tmp/yum-root-Mncwuv/amazon-ssm-agent.rpm: amazon-ssm-agent-2.0.822.0-1.x86_64
Marking /var/tmp/yum-root-Mncwuv/amazon-ssm-agent.rpm to be installed
Resolving Dependencies
amzn-main/latest                                                | 2.1 kB  00:00:00
amzn-updates/latest                                             | 2.3 kB  00:00:00
--> Running transaction check
---> Package amazon-ssm-agent.x86_64 0:2.0.822.0-1 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

=======================================================================================
 Package                Arch         Version             Repository               Size
=======================================================================================
Installing:
 amazon-ssm-agent       x86_64       2.0.822.0-1         /amazon-ssm-agent        17 M

Transaction Summary
=======================================================================================
Install  1 Package

Total size: 17 M
Installed size: 17 M
Downloading packages:
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : amazon-ssm-agent-2.0.822.0-1.x86_64                                 1/1
amazon-ssm-agent start/running, process 7104
  Verifying  : amazon-ssm-agent-2.0.822.0-1.x86_64                                 1/1

Installed:
  amazon-ssm-agent.x86_64 0:2.0.822.0-1

Complete!
[ec2-user@ip-10-0-2-141 ~]$

Simple Systems Manager エージェントの起動確認

[ec2-user@ip-10-0-2-141 ~]$ sudo status amazon-ssm-agent
amazon-ssm-agent start/running, process 7104
[ec2-user@ip-10-0-2-141 ~]$

ちなみにAmazon Linux の場合 Upstartでの管理になります。

[ec2-user@ip-10-0-2-141 ~]$ sudo initctl list |grep amazon-ssm-agent
amazon-ssm-agent start/running, process 7104
[ec2-user@ip-10-0-2-141 ~]$

Simple Systems Manager への登録確認

EC2 の「SYSTEM MANAGER共有リソース」の「マネージドインスタンス」を選択します。

エージェントをインストールしたインスタンスが登録されていることが確認できます。
2017-06-21-15-11-56.png

続きを読む

稼働しているEC2インスタンスからAMIを作成するshell script

久しぶりの投稿です。

最近、稼働しているEC2インスタンスのAMIをリアルタイムで作成する必要が有ったので、shell scriptを使って実装してみました。

  • 前提

    • AWS CLIの設定(インストール~ aws configure での設定)が完了している
    • jqは使用せず、AWS CLIのオプション(filters, query, output等)のみで対応する

処理の流れはこんな感じ↓です

  1. “Instance State”が running で “tag” に Name が設定されているインスタンスのInstanceIdを取得して変数に格納する
  2. 1で取得したInstanceIdをキーにしてインスタンスに設定されている “tag” の Name を取得して変数に格納する
  3. 格納した変数をそれぞれ配列に格納する
  4. InstanceIdをキーにAMIを作成(AMI NameとDescriptionには2で取得した “tag” の Name + “-” + 今日の日付を設定する)
  5. 4で作成したAMIの “tag” の Name に2で取得した “tag” の Name + “-” + 今日の日付を設定する
  6. キーにしたInstanceIdが存在する間、4~5を繰り返す

ソースは以下↓です。

#!/bin/bash

set -eu

TODAY=`date '+%Y%m%d'`
instance_ids=""
instance_names=""
source_ids=""
source_names=""
j=0

## get instance_ids (only status is 'running' and tag-key=Name)
instance_ids=`aws ec2 describe-instances --filters "Name=instance-state-name,Values=running" "Name=tag-key,Values=Name" --query "Reservations[].Instances[].InstanceId" --output text`

## get instance_Name (only status is 'running' and tag-key=Name)
instance_names=`aws ec2 describe-instances --instance-ids ${instance_ids} --filters "Name=tag-key,Values=Name" --query "Reservations[].Instances[].Tags[].Value" --output text`

source_ids=($instance_ids)
source_names=($instance_names)

for i in "${source_ids[@]}"
do
  ami_id=""

  ## create ami
  ami_id=`aws ec2 create-image --instance-id ${i} --name "${source_names[$j]}-${TODAY}" --description "${source_names[$j]}-${TODAY}" --no-reboot --output text`

  ### put tags to ami
  aws ec2 create-tags --resources ${ami_id} --tags Key=Name,Value="${source_names[$j]}-${TODAY}"

  ## count up
  j=$((j+1))
done

exit 0

  • 注意事項

    • AMIを作成するインスタンス情報の取得の際にOWNER ID等での制限をしていないので、クロスアカウントでの使用の際には注意が必要
    • インスタンスの状態が’running’になっているもの全てに対してAMIを作成するようになっているので、台数が多い場合には注意が必要

スクリプトは こちら にもありますので、ご自由に使ってください。
処理自体は非常にシンプルになっています(自分の実力でもありますが…)
基本的な動作確認はしていますが、もし何かありましたらそっと修正して教えていただくと非常にありがたいです!

続きを読む

EC2上にhubotを設置してslackと連携させる

最近ChatOpsに興味が出てきたので、とりあえずEC2上にhubotが動作する環境を作り、slackと連携出来るようにしました。

前提

以下の環境が既にあること。

  • EC2
  • slack

環境構築

  • nvm〜hubotの起動までは全てEC2上での作業です。

nvmのインストール

  • curlでインストールします
sudo curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash
  • .bash_profileに以下を追記します
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm
  • nvmコマンドが実行できることを確認します
nvm help

nodejsのインストール

  • nvmでnodejsのv6.11.0をインストールします
  • 公式サイトを見ると includes npm 3.10.10 と書いてあるのでnpmも一緒にインストールされます
nvm install 6.11.0
  • インストールされたことを確認します
node -v
npm -v

redisのインストール

  • elasticacheは使わずAmazon Linux上にredisをインストールします
  • epelだとバージョンが古いようなのでremiを利用します
sudo rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
sudo yum --enablerepo=remi install redis
  • redisを起動させます
sudo service redis start

hubotのインストール

  • hubotの環境を構築します
  • npmでhubot,coffescript,redis,yo,generator-hubotをインストールします
npm install -g hubot coffee-script redis yo generator-hubot
  • hubotをインストールします
mkdir -p ~/deploy-sushi
cd deploy-sushi
yo hubot
  • 対話式で色々聞かれるので自身の環境に合わせて入力してください
? ==========================================================================
We're constantly looking for ways to make yo better!
May we anonymously report usage statistics to improve the tool over time?
More info: https://github.com/yeoman/insight & http://yeoman.io
========================================================================== Y/no


省略


? Owner (User <user@example.com>)
? Bot name (deploy-sushi)
? Description (A simple helpful robot for your Company)
? Bot adapter (campfire)
  • 今回は?4つに以下のように答えました
? Owner {MY_EMAIL}
? Bot name deploy-sushi
? Description this is deploy boy
? Bot adapter slack
  • hubotを立ち上げて動作確認をします
bin/hubot
  • いろんなメッセージが出た後にEnterを押せば deploy-sushi> という対話が始まります
deploy-sushi> deploy-sushi ping
deploy-sushi> PONG
  • hubot-slackをnpmでインストールします
npm install hubot-slack

slackと連携させる

  • slack上の操作に変わります
  • slackのサイドメニューを開き Configure Apps を開きます

スクリーンショット 2017-06-17 14.12.32.png

  • 画面上部に Hubot を入力し、検索結果をクリックします

スクリーンショット 2017-06-17 14.16.11.png

  • Installボタンをクリックします

スクリーンショット 2017-06-17 14.17.17.png

  • hubotの名前を入力して Add Hubot Integrationをクリックします

スクリーンショット 2017-06-17 14.18.21.png

  • 作成が完了したらhubotの詳細画面に遷移するので画面上部の Setup Instructions のところに記載されている HUBOT_SLACK_TOKEN={YOUR_API_TOKEN} をコピーしておきます
  • 画像を変更する・・・など何か編集を加えた場合、画面下部の保存ボタンをクリックして更新してください

  • EC2に戻り以下の環境変数をexportします

echo 'export HUBOT_SLACK_TOKEN={YOUR_API_TOKEN}' >> ~/.bash_profile
source ~/.bash_profile
  • アダプタを指定してhubotを起動します
bin/hubot --adapter slack
  • slackのどこかのチャンネルにhubotをinviteして動作確認します

スクリーンショット 2017-06-17 14.29.14.png

  • 動作確認ができたらEC2起動時に自動でhubotが立ち上がるようにします

hubotの永続実行

  • foreverをnpmでインストールします
npm install -g forever
  • bin/hubot を書き換えます
以下を追記
forever start -w -c coffee node_modules/.bin/hubot --adapter slack


以下をコメントアウト
exec node_modules/.bin/hubot --name "deploy-sushi" "$@"

  • バックグラウンドで動くことを確認します
bin/hubot
ps aux | grep hubot

EC2起動時のスクリプト作成

sudo vi /etc/init.d/start_deploy_hubot
#!/bin/bash
#chkconfig: 2345 99 10
#descpriction: start deploy hubot
DIR="/home/ec2-user/deploy-sushi"
cd $DIR
bin/hubot
  • 実行権限を付与します
sudo chmod 755 /etc/init.d/start_deploy_hubot
  • サービスに追加します
sudo chkconfig --add start_deploy_hubot
  • EC2インスタンスを再起動してhubotが立ち上がっていることを確認してください

hubotが起動しなかった・・・

  • npm,foreverのパスがec2-userにしか通っていないかったのでrootでnpm,foreverするとコマンドが見つからない。
  • トークンもec2-userの .bash_profile に記述していたので見つからない。
  • とりいそぎ bin/hubot に以下を追記しました
    • npm install よりも前に追記してください
export PATH="/home/ec2-user/.nvm/versions/node/v6.11.0/bin:$PATH"
export HUBOT_SLACK_TOKEN={YOUR_API_TOKEN}
  • EC2を再起動して、hubotが立ち上がっていることを確認してください

    • プロセスの確認をします
ps aux | grep hubot
  • slackのチャンネルでもpingして返答があることを確認してください

おわり

hubotがサーバ上で動き続けるようになったので、次回はslackからgit操作を出来るようにしようと思います。

続きを読む

terraformでS3バケットのACLに権限を付与する

CloudFrontのアクセスログをS3に保存させる事はごくごく普通にあると思いますが、terraformで環境を構築しているとS3作成時に特殊なACLは設定できず、CloudFront作成時にログ出力指定をしていると権限不足で落ちてしまいます。

参考: アクセスログ – Amazon CloudFront

terraformだけで解決できれば一番良いのですが、2017年6月現在ではissueが出ているものの反映されるのはまだ先になりそうな感じです。

GUIからぽちぽちしてると勝手にACLを作ってくれるので、手で作った後にterraformに記載するとかいう意味のない作業をしていたのですが、そんな作業やりたくなかったので無理やり気味ではありますがterraform applyを実行するだけでS3にACLを付与し、CloudFrontの構築まで一気通貫で行えるようにちょっとした工夫を加えてみました。

準備するもの

linux/mac前提です。windowsの場合はbash on windowsであれば動作可能です。

必須

  • terraform
  • AWS CLI

推奨

  • direnv

準備するスクリプト

bin/AssignAwsdatafeedsAcl
#!/bin/sh

# 自身のディレクトリパスを取得
CURRENT_DIR=$(cd $(dirname $0);pwd)/

# 引数のチェックを行う
if [ $# -ne 1 ]; then
    exit 1
fi

# バケット名を引数から取得
BUCKET_NAME=$1

TEMP_FILE=$(mktemp)

# S3待ち(waitが無い場合にエラーが発生)
sleep 2

# 現在のバケットACL情報を取得する
aws s3api get-bucket-acl --bucket ${BUCKET_NAME} > ${TEMP_FILE}
if [ $? -ne 0 ]; then
    # テンポラリファイルを削除する
    rm -f ${TEMP_FILE}
    exit 1
fi

# ACLにawsdatafeeds権限が含まれていない場合は追加する
grep awsdatafeeds ${TEMP_FILE}
if [ $? -eq 1 ]; then
    # 追記する文字列を取得
    INPUT_LINES=$(perl -p -e 's/n/\n/' ${CURRENT_DIR}acl.txt)
    if [ $? -ne 0 ]; then
        # テンポラリファイルを削除する
        rm -f ${TEMP_FILE}
        exit 1
    fi

    # 権限情報を追記
    sed -i -e "s/("Grants": *[)/1n${INPUT_LINES}/" ${TEMP_FILE}
    if [ $? -ne 0 ]; then
        # テンポラリファイルを削除する
        rm -f ${TEMP_FILE}
        exit 1
    fi

    # 権限情報をS3バケットに反映
    aws s3api put-bucket-acl --bucket ${BUCKET_NAME} --access-control-policy "$(cat ${TEMP_FILE})"
    if [ $? -ne 0 ]; then
        # テンポラリファイルを削除する
        rm -f ${TEMP_FILE}
        exit 1
    fi
fi

# テンポラリファイルを削除する
rm -f ${TEMP_FILE}

exit 0
bin/acl.txt
        {
            "Permission": "FULL_CONTROL",
            "Grantee": {
                "DisplayName": "awsdatafeeds",
                "ID": "c4c1ede66af53448b93c283ce9448c4ba468c9432aa01d700d3878632f77d2d0",
                "Type": "CanonicalUser"
            }
        },

上記2ファイルをパスが通ったところに配置しておきます。
シェルスクリプトの方にはちゃんと実行権限を付与しましょう。

ちなみにdirenvを使って、terraform実行パスなどでPATHを追加するのが推奨です。

.envrc
export AWS_DEFAULT_PROFILE=[AWSプロファイル名]
export AWS_DEFAULT_REGION=[デフォルトとするリージョン]

export AWS_PROFILE=$AWS_DEFAULT_PROFILE
export AWS_REGION=$AWS_DEFAULT_REGION

PATH_add `pwd`/bin

terraformの記述

s3.tf
resource "aws_s3_bucket" "sample" {
    bucket = "sample-cfn-logs"
    acl    = "private"
    tags {
         Name = "sample-cfn-logs"
    }

    provisioner "local-exec" {
         command = "AssignAwsdatafeedsAcl ${aws_s3_bucket.test.bucket}"
    }
}

概要

terraformのprovisionerにlocal-execという、実行マシンでのコマンド実行機能があります。

これはリソースが作成されたのちに実行されるので、S3リソースが作成された後にシェルスクリプトを実行し、AWS CLIを利用してS3バケットにACLを付与しています。

上記スクリプトでは下記の様な処理を行っています。

  1. aws cli: S3バケットの設定済みACLを取得
  2. shell: 取得したACLにawsdatafeedsの権限が付与されているかチェックする
  3. shell: 権限がない場合、権限の内容を取得したACLに追記する
  4. aws cli: 改変したACL情報をS3バケットに反映する

S3バケットが構築されてからAPIで叩けるまでに微妙なタイムラグがあるらしく、sleepが入っていないとAPIを叩いた時にバケットが存在しないと怒られてしまいます。

ちゃんとするならば、APIのレスポンスを見て待つ処理を入れるのが良いのですが、terraformがMulti ACLに対応するまでの暫定的な対応なのでsleepで濁しています。

配置したスクリプトにパスが通っていないとlocal-execの指定時にわざわざパスを書いてあげる必要があるので、direnv使ってパス通しちゃいましょう。

相対パスがどこからになるのか知らない。

ちなみにacl.txtの内容を変えると好きな権限を入れれます。でもあまり使わないですし変更検知もされないので推奨はしません。

どうしてもという場合はaws_s3_bucketリソースの代わりにnull_resourceリソースを使って毎回スクリプトをキックするようにした上で、前回実行のACLと反映するACLに差分がある時に実行するなど工夫をしてみてください。

結論

はよterraform自体で対応して。

続きを読む

AWS LambdaにMecabを乗せてPythonで動かす

実装するにあたりこちらのサイトを参考にさせて頂きました!
ありがとうございます!

pythonのバージョンは2.7
localの環境はWindows bashです。

はじめに

Mecabはコードにネイティブバイナリを使用しているため、Lambdaの実行環境と同じ環境でデプロイパッケージを作成する必要があります。
(ソースはここです。Lambdaの実行環境ドキュメント
なので、EC2でAmazon Linuxインスタンスを立て、その中でデプロイパッケージを作成していきます。

必要なファイル及び作成環境の準備

まず必要なファイルをローカル上にDLします。
① mecab-0.996.tar.gz
② mecab-ipadic-2.7.0-20070801.tar.gz
上記2つはここからDLできます
③ mecab-python-0.996.tar.gz
ここからDLできます。

次にlinuxインスタンスにsshでログインします。
やり方分からないよって方は、AWSのドキュメントの「Linuxインスタンスへの接続」の項を参照していただけると分かると思います!

Linuxインスタンスは、立てたばかりの状態ではコンパイラなどが入っていないのでインストールします。

Linuxインスタンス上
[ec2-user ~]$ sudo yum groupinstall "Development Tools"

そしてLinuxインスタンスのホームディレクトリにmecab-functionディレクトリを作成します。

Linuxインスタンス上
$mkdir mecab-function

先ほどDLした①〜③のファイルを全てLinuxインスタンスのホームディレクトリに送信します。
(こちらも詳細はドキュメントの「scpを利用してファイルを転送するには」の項を参照していただけると分かると思います。)

ローカル上
scp -i /path/秘密キーファイルの名前 /path/mecab-0.996.tar.gz ec2-user@インスタンスのパブリックDNS名:~

scp -i /path/秘密キーファイルの名前 /path/mecab-ipadic-2.7.0-20070801.tar.gz ec2-user@インスタンスのパブリックDNS名:~

scp -i /path/秘密キーファイルの名前 /path/mecab-python-0.996.tar.gz ec2-user@インスタンスのパブリックDNS名:~

ここまで出来たら、Linuxインスタンスのホームディレクトリには以下のファイルやディレクトリが存在してると思います。

mecab-function/
mecab-0.996.tar.gz
mecab-ipadic-2.7.0-20070801.tar.gz
mecab-python-0.996.tar.gz

次は、今落としたmecabなどをディレクトリにインストールしていきます。

ディレクトリへのインストール

ここからは、Linuxインスタンス上での操作となります。
まず、mecabとmecab-ipadicを解凍してインストールしていきます。

① mecabのインストール

$tar zvxf mecab-0.996.tar.gz
$cd mecab-0.996
$./configure --prefix=$DIR_HOME/local --with-charset=utf8
$make
$make install

“$DIR_HOME”は、mecab-functionディレクトリのパスです。
mecab-functionディレクトリ内で以下のコマンドを実行すると、パスを取得できます。

$pwd

“–prefix=ディレクトリのパス”で、mecabをインストールするディレクトリを指定しています。

“–with-charset=utf8″は、mecabをutf8で使用するという宣言をしています。
この宣言をしないと、mecabが上手くparseできなかったり、エラーが出たりするので注意してください。
ちなみに、”–enable-utf8-only”とは違うのでこちらも注意してください。

“make install”が完了したら、mecab-0.996ディレクトリから、ホームディレクトリに戻ります。

② mecab-ipadicのインストール

$tar zvxf mecab-ipadic-2.7.0-20070801.tar.gz
$cd mecab-ipadic-2.7.0-20070801

# mecabの場所をPATHに追加
$export PATH=$DIR_HOME/local/bin:$PATH

$./configure --prefix=$DIR_HOME/local --with-charset=utf8
$make
$make install

“$DIR_HOME”など、①と同じです。
“make install”が終了したら、ホームディレクトリに戻ります。
ここで、”mecab”コマンドを実行した時に動作すればmecabとmecab-ipadicのインストールは正しくできています。

③mecab-pythonのインストール

$pip install mecab-python-0.996.tar.gz -t $DIR_HOME/lib

pipは、-tでライブラリのインストール先を指定できます。

ここまで出来ると、mecab-function内は以下の構造になっていると思います。

mecab-function
|- lib/
|- local/
|- exclude.lst
|- function.py

function.pyとexclude.lstって何?ってなりますよね?
次でその2つのファイルについて説明します。

function.pyの説明

Lambdaのハンドラー関数を含むpythonのコードを説明します。
今回は、入力された日本語を単に分かち書きして出力するハンドラー関数を作成しました。

function.py
#coding: utf-8

import json
import os
import ctypes

#ライブライのパスを取得
libdir = os.path.join(os.getcwd(), "local", "lib")
libmecab = ctypes.cdll.LoadLibrary(os.path.join(libdir, "libmecab.so.2"))

import MeCab

#mecabの辞書(ipadic)へのパスを取得
dicdir = os.path.join(os.getcwd(), "local", "lib", "mecab", "dic", "ipadic")
#mecabのrcファイルへのパスを取得
rcfile = os.path.join(os.getcwd(), "local", "etc", "mecabrc")
tagger = MeCab.Tagger("-d{} -r{}".format(dicdir, rcfile))


def handler(event, context):
    """
    event = {
        "sentence": 分かち書きしたい文章
    }
    """
    sentence = event.get("sentence")
    encode_sentence = sentence.encode("utf_8")
    node = tagger.parseToNode(encode_sentence)
    result = []
    while node:
        surface = node.surface
        if surface != "":
            test_list = [surface]
            print surface
            print test_list
            decode_surface = surface.decode("utf_8")
            result.append(decode_surface)
        node = node.next
    return result

“ctypes.cdll.LoadLibrary()”で、動的リンクライブラリをロードしています。
また、MeCab.Taggerクラスのオブジェクトを作成する際に、辞書とmecabrcファイルへのパスを指定してあげる必要があります。

exclude.lstの説明

zipファイル作成時に除外するファイルの一覧です。

exclude.txt
*.dist-info/*
*.egg-info
*.pyc
exclude.lst
local/bin/*
local/include/*
local/libexec/*
local/share/*

デプロイパッケージの作成

いよいよデプロイパッケージの作成です。
mecab-function/libにある

_MeCab.so
MeCab.py
MeCab.pyc

の3つのファイルをmecab-fucntionに移します。
ここまで行えば、以下のような感じになってると思います。

mecab-function
|- lib/
|- local/
|- _MeCab.so
|- exclude.lst
|- function.py
|- MeCab.py
|- MeCab.pyc

そして、mecab-functionディレクトリで以下のコマンドを実行すると、mecab-function.zipが作成されます。

$zip -r9 mecab-function.zip * -x@exclude.lst

mecab-function.zipが以下のような構造になっていたら完成です。

mecab-function
|- lib/
|- local/
|- _MeCab.so
|- function.py
|- MeCab.py
|- MeCab.pyc

あとは、mecab-function.zipをLambdaにあげれば完了です!
Linuxインスタンス上にあるmecab-function.zipをscpコマンドを使ってローカルに転送してAWS Lambdaのコンソール画面から直接あげてもいいですし、S3に飛ばしてからあげてもいいと思います。
自分は、ローカルに一旦転送しました。

ローカルに転送するには、ローカルで以下のコマンドを実行します。

ローカル上
scp -i /path/秘密キーファイルの名前 ec2-user@インスタンスのパブリックDNS名:~/mecab-function/mecab-function.zip /転送したい場所のpath(ローカル上)/mecab-function.zip

このコマンドの詳しい内容は、おなじみAWSのドキュメントの「scpを利用してファイルを転送するには」を参照していただければと思います。

まとめ

最後に重要なポイントをまとめたいと思います。

① デプロイパッケージは、Linuxインスタンス上で作成する!
② mecabをimportする時に、動的リンクライブラリをロードする。
③ MeCab.Taggerでオブジェクト作成時に、辞書とmecabrcファイルへのパスを渡してあげる。
④デプロイパッケージの構造は、この記事で示したようにする。

ここまで読んで頂きありがとうございました。

続きを読む

AWS CLIで今俺どのアクセスキー使ってるんだ?を調べる方法

aws cliを利用している際に複数のAWSアカウントやアクセスキーを使っていると、どのアクセスキーを使っているのかわからなくなることがある。

そんな時にこれを打ちましょう。

bash
aws configure list --profile test
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                     test           manual    --profile
access_key     ****************xxxx shared-credentials-file
secret_key     ****************xxxx shared-credentials-file
    region                us-east-1      config-file    ~/.aws/config

幸せになれました。

続きを読む

[JAWS-UG CLI] CloudFormation:#7 テンプレートの作成 (デザイナの利用)

デザイナーによるテンプレート作成の例

ウォークスルー: AWS CloudFormation デザイナー を使用して基本的なウェブサーバーを作成します。: http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/working-with-templates-cfn-designer-walkthrough-createbasicwebserver.html

ステップ3まで実施します。

スクリーンショット 2017-06-12 17.32.57.png

テンプレートの作成結果例

テンプレートの例
{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Metadata": {
        "AWS::CloudFormation::Designer": {
            "2e84feb3-cb7f-47d8-a50b-73d6e89de7bc": {
                "size": {
                    "width": 710,
                    "height": 220
                },
                "position": {
                    "x": 100,
                    "y": 130
                },
                "z": 0,
                "embeds": [
                    "25d45e53-d776-4534-b244-b9a49c15b6ff",
                    "09677cca-68f1-478f-842e-32018f9bf5fa",
                    "4a16106f-3988-4902-ba28-2fcf5bc1d48d"
                ]
            },
            "09677cca-68f1-478f-842e-32018f9bf5fa": {
                "size": {
                    "width": 140,
                    "height": 140
                },
                "position": {
                    "x": 180,
                    "y": 200
                },
                "z": 1,
                "parent": "2e84feb3-cb7f-47d8-a50b-73d6e89de7bc",
                "embeds": [
                    "5227546f-a488-4322-b146-17e159d2657f"
                ]
            },
            "5227546f-a488-4322-b146-17e159d2657f": {
                "size": {
                    "width": 60,
                    "height": 60
                },
                "position": {
                    "x": 228,
                    "y": 236
                },
                "z": 2,
                "parent": "09677cca-68f1-478f-842e-32018f9bf5fa",
                "embeds": [],
                "dependson": [
                    "1e86fca9-84d1-4d05-9e68-efb1df27d4c5"
                ],
                "isrelatedto": [
                    "25d45e53-d776-4534-b244-b9a49c15b6ff"
                ]
            },
            "25d45e53-d776-4534-b244-b9a49c15b6ff": {
                "size": {
                    "width": 60,
                    "height": 60
                },
                "position": {
                    "x": 480,
                    "y": 180
                },
                "z": 1,
                "parent": "2e84feb3-cb7f-47d8-a50b-73d6e89de7bc",
                "embeds": []
            },
            "0e5d222f-40cb-4c52-b251-80c744846be5": {
                "size": {
                    "width": 60,
                    "height": 60
                },
                "position": {
                    "x": 880,
                    "y": 220
                },
                "z": 0,
                "embeds": []
            },
            "5637b4f3-df45-40fb-a91d-dae734a3abdd": {
                "source": {
                    "id": "0e5d222f-40cb-4c52-b251-80c744846be5"
                },
                "target": {
                    "id": "2e84feb3-cb7f-47d8-a50b-73d6e89de7bc"
                },
                "z": 0
            },
            "75da879a-0efa-427e-8071-077ba3a16212": {
                "source": {
                    "id": "0e5d222f-40cb-4c52-b251-80c744846be5"
                },
                "target": {
                    "id": "2e84feb3-cb7f-47d8-a50b-73d6e89de7bc"
                },
                "z": 0
            },
            "4a16106f-3988-4902-ba28-2fcf5bc1d48d": {
                "size": {
                    "width": 140,
                    "height": 140
                },
                "position": {
                    "x": 650,
                    "y": 170
                },
                "z": 1,
                "parent": "2e84feb3-cb7f-47d8-a50b-73d6e89de7bc",
                "embeds": [
                    "1e86fca9-84d1-4d05-9e68-efb1df27d4c5"
                ],
                "dependson": [
                    "09677cca-68f1-478f-842e-32018f9bf5fa"
                ]
            },
            "1e86fca9-84d1-4d05-9e68-efb1df27d4c5": {
                "size": {
                    "width": 60,
                    "height": 60
                },
                "position": {
                    "x": 700,
                    "y": 240
                },
                "z": 2,
                "parent": "4a16106f-3988-4902-ba28-2fcf5bc1d48d",
                "embeds": [],
                "references": [
                    "0e5d222f-40cb-4c52-b251-80c744846be5"
                ],
                "dependson": [
                    "0e5d222f-40cb-4c52-b251-80c744846be5"
                ]
            },
            "a1cc9b57-b454-483b-8e86-4e4e0cd9cacb": {
                "source": {
                    "id": "1e86fca9-84d1-4d05-9e68-efb1df27d4c5"
                },
                "target": {
                    "id": "0e5d222f-40cb-4c52-b251-80c744846be5"
                },
                "z": 3
            },
            "522ea7e3-d856-43d9-97c0-ab3da44fc19c": {
                "source": {
                    "id": "5227546f-a488-4322-b146-17e159d2657f"
                },
                "target": {
                    "id": "1e86fca9-84d1-4d05-9e68-efb1df27d4c5"
                },
                "z": 4
            },
            "b617925c-337f-46dc-bf6c-d8d61ff8d687": {
                "source": {
                    "id": "4a16106f-3988-4902-ba28-2fcf5bc1d48d"
                },
                "target": {
                    "id": "09677cca-68f1-478f-842e-32018f9bf5fa"
                },
                "z": 5
            },
            "b8d8deca-616b-4d76-8797-873228e79f9d": {
                "source": {
                    "id": "1e86fca9-84d1-4d05-9e68-efb1df27d4c5"
                },
                "target": {
                    "id": "0e5d222f-40cb-4c52-b251-80c744846be5"
                },
                "z": 3
            },
            "25d0fe02-7713-433a-88a0-7f85890eb289": {
                "source": {
                    "id": "1e86fca9-84d1-4d05-9e68-efb1df27d4c5"
                },
                "target": {
                    "id": "0e5d222f-40cb-4c52-b251-80c744846be5"
                },
                "z": 4
            },
            "f818e69a-a5cb-4569-92b0-a5bbab980380": {
                "source": {
                    "id": "4a16106f-3988-4902-ba28-2fcf5bc1d48d"
                },
                "target": {
                    "id": "09677cca-68f1-478f-842e-32018f9bf5fa"
                },
                "z": 3
            }
        }
    },
    "Resources": {
        "VPC": {
            "Type": "AWS::EC2::VPC",
            "Properties": {
                "EnableDnsSupport": "true",
                "EnableDnsHostnames": "true",
                "CidrBlock": "10.0.0.0/16"
            },
            "Metadata": {
                "AWS::CloudFormation::Designer": {
                    "id": "2e84feb3-cb7f-47d8-a50b-73d6e89de7bc"
                }
            }
        },
        "PublicSubnet": {
            "Type": "AWS::EC2::Subnet",
            "Properties": {
                "VpcId": {
                    "Ref": "VPC"
                },
                "CidrBlock": "10.0.0.0/24"
            },
            "Metadata": {
                "AWS::CloudFormation::Designer": {
                    "id": "09677cca-68f1-478f-842e-32018f9bf5fa"
                }
            }
        },
        "WebServerInstance": {
            "Type": "AWS::EC2::Instance",
            "Properties": {
                "InstanceType": {
                    "Ref": "InstanceType"
                },
                "ImageId": {
                    "Fn::FindInMap": [
                        "AWSRegionArch2AMI",
                        {
                            "Ref": "AWS::Region"
                        },
                        {
                            "Fn::FindInMap": [
                                "AWSInstanceType2Arch",
                                {
                                    "Ref": "InstanceType"
                                },
                                "Arch"
                            ]
                        }
                    ]
                },
                "KeyName": {
                    "Ref": "KeyName"
                },
                "NetworkInterfaces": [
                    {
                        "GroupSet": [
                            {
                                "Ref": "WebServerSecurityGroup"
                            }
                        ],
                        "AssociatePublicIpAddress": "true",
                        "DeviceIndex": "0",
                        "DeleteOnTermination": "true",
                        "SubnetId": {
                            "Ref": "PublicSubnet"
                        }
                    }
                ],
                "UserData": {
                    "Fn::Base64": {
                        "Fn::Join": [
                            "",
                            [
                                "#!/bin/bash -xen",
                                "yum install -y aws-cfn-bootstrapn",
                                "# Install the files and packages from the metadatan",
                                "/opt/aws/bin/cfn-init -v ",
                                "         --stack ",
                                {
                                    "Ref": "AWS::StackName"
                                },
                                "         --resource WebServerInstance ",
                                "         --configsets All ",
                                "         --region ",
                                {
                                    "Ref": "AWS::Region"
                                },
                                "n",
                                "# Signal the status from cfn-initn",
                                "/opt/aws/bin/cfn-signal -e $? ",
                                "         --stack ",
                                {
                                    "Ref": "AWS::StackName"
                                },
                                "         --resource WebServerInstance ",
                                "         --region ",
                                {
                                    "Ref": "AWS::Region"
                                },
                                "n"
                            ]
                        ]
                    }
                }
            },
            "Metadata": {
                "AWS::CloudFormation::Designer": {
                    "id": "5227546f-a488-4322-b146-17e159d2657f"
                },
                "AWS::CloudFormation::Init": {
                    "configSets": {
                        "All": [
                            "ConfigureSampleApp"
                        ]
                    },
                    "ConfigureSampleApp": {
                        "packages": {
                            "yum": {
                                "httpd": []
                            }
                        },
                        "files": {
                            "/var/www/html/index.html": {
                                "content": {
                                    "Fn::Join": [
                                        "n",
                                        [
                                            "<h1>Congratulations, you have successfully launched the AWS CloudFormation sample.</h1>"
                                        ]
                                    ]
                                },
                                "mode": "000644",
                                "owner": "root",
                                "group": "root"
                            }
                        },
                        "services": {
                            "sysvinit": {
                                "httpd": {
                                    "enabled": "true",
                                    "ensureRunning": "true"
                                }
                            }
                        }
                    }
                }
            },
            "DependsOn": [
                "PublicRoute"
            ]
        },
        "WebServerSecurityGroup": {
            "Type": "AWS::EC2::SecurityGroup",
            "Properties": {
                "VpcId": {
                    "Ref": "VPC"
                },
                "GroupDescription": "Allow access from HTTP and SSH traffic",
                "SecurityGroupIngress": [
                    {
                        "IpProtocol": "tcp",
                        "FromPort": "80",
                        "ToPort": "80",
                        "CidrIp": "0.0.0.0/0"
                    },
                    {
                        "IpProtocol": "tcp",
                        "FromPort": "22",
                        "ToPort": "22",
                        "CidrIp": {
                            "Ref": "SSHLocation"
                        }
                    }
                ]
            },
            "Metadata": {
                "AWS::CloudFormation::Designer": {
                    "id": "25d45e53-d776-4534-b244-b9a49c15b6ff"
                }
            }
        },
        "InternetGateway": {
            "Type": "AWS::EC2::InternetGateway",
            "Properties": {},
            "Metadata": {
                "AWS::CloudFormation::Designer": {
                    "id": "0e5d222f-40cb-4c52-b251-80c744846be5"
                }
            }
        },
        "EC2VPCG1BZXP": {
            "Type": "AWS::EC2::VPCGatewayAttachment",
            "Properties": {
                "InternetGatewayId": {
                    "Ref": "InternetGateway"
                },
                "VpcId": {
                    "Ref": "VPC"
                }
            },
            "Metadata": {
                "AWS::CloudFormation::Designer": {
                    "id": "5637b4f3-df45-40fb-a91d-dae734a3abdd"
                }
            }
        },
        "EC2VPCGO08Z": {
            "Type": "AWS::EC2::VPCGatewayAttachment",
            "Properties": {
                "InternetGatewayId": {
                    "Ref": "InternetGateway"
                },
                "VpcId": {
                    "Ref": "VPC"
                }
            },
            "Metadata": {
                "AWS::CloudFormation::Designer": {
                    "id": "75da879a-0efa-427e-8071-077ba3a16212"
                }
            }
        },
        "PublicRouteTable": {
            "Type": "AWS::EC2::RouteTable",
            "Properties": {
                "VpcId": {
                    "Ref": "VPC"
                }
            },
            "Metadata": {
                "AWS::CloudFormation::Designer": {
                    "id": "4a16106f-3988-4902-ba28-2fcf5bc1d48d"
                }
            },
            "DependsOn": [
                "PublicSubnet"
            ]
        },
        "PublicRoute": {
            "Type": "AWS::EC2::Route",
            "Properties": {
                "DestinationCidrBlock": "0.0.0.0/0",
                "RouteTableId": {
                    "Ref": "PublicRouteTable"
                },
                "GatewayId": {
                    "Ref": "InternetGateway"
                }
            },
            "Metadata": {
                "AWS::CloudFormation::Designer": {
                    "id": "1e86fca9-84d1-4d05-9e68-efb1df27d4c5"
                }
            },
            "DependsOn": [
                "InternetGateway"
            ]
        },
        "EC2SRTA3CLE1": {
            "Type": "AWS::EC2::SubnetRouteTableAssociation",
            "Properties": {
                "RouteTableId": {
                    "Ref": "PublicRouteTable"
                },
                "SubnetId": {
                    "Ref": "PublicSubnet"
                }
            },
            "Metadata": {
                "AWS::CloudFormation::Designer": {
                    "id": "f818e69a-a5cb-4569-92b0-a5bbab980380"
                }
            }
        }
    },
    "Parameters": {
        "InstanceType": {
            "Description": "WebServer EC2 instance type",
            "Type": "String",
            "Default": "t2.micro",
            "AllowedValues": [
                "t1.micro",
                "t2.micro",
                "t2.small",
                "t2.medium",
                "m1.small",
                "m1.medium",
                "m1.large",
                "m1.xlarge",
                "m2.xlarge",
                "m2.2xlarge",
                "m2.4xlarge",
                "m3.medium",
                "m3.large",
                "m3.xlarge",
                "m3.2xlarge",
                "c1.medium",
                "c1.xlarge",
                "c3.large",
                "c3.xlarge",
                "c3.2xlarge",
                "c3.4xlarge",
                "c3.8xlarge",
                "c4.large",
                "c4.xlarge",
                "c4.2xlarge",
                "c4.4xlarge",
                "c4.8xlarge",
                "g2.2xlarge",
                "r3.large",
                "r3.xlarge",
                "r3.2xlarge",
                "r3.4xlarge",
                "r3.8xlarge",
                "i2.xlarge",
                "i2.2xlarge",
                "i2.4xlarge",
                "i2.8xlarge",
                "d2.xlarge",
                "d2.2xlarge",
                "d2.4xlarge",
                "d2.8xlarge",
                "hi1.4xlarge",
                "hs1.8xlarge",
                "cr1.8xlarge",
                "cc2.8xlarge",
                "cg1.4xlarge"
            ],
            "ConstraintDescription": "must be a valid EC2 instance type."
        },
        "KeyName": {
            "Description": "Name of an EC2 KeyPair to enable SSH access to the instance.",
            "Type": "AWS::EC2::KeyPair::KeyName",
            "ConstraintDescription": "must be the name of an existing EC2 KeyPair."
        },
        "SSHLocation": {
            "Description": " The IP address range that can be usedto access the web server using SSH.",
            "Type": "String",
            "MinLength": "9",
            "MaxLength": "18",
            "Default": "0.0.0.0/0",
            "AllowedPattern": "(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})",
            "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
        }
    },
    "Mappings": {
        "AWSInstanceType2Arch": {
            "t1.micro": {
                "Arch": "PV64"
            },
            "t2.micro": {
                "Arch": "HVM64"
            },
            "t2.small": {
                "Arch": "HVM64"
            },
            "t2.medium": {
                "Arch": "HVM64"
            },
            "m1.small": {
                "Arch": "PV64"
            },
            "m1.medium": {
                "Arch": "PV64"
            },
            "m1.large": {
                "Arch": "PV64"
            },
            "m1.xlarge": {
                "Arch": "PV64"
            },
            "m2.xlarge": {
                "Arch": "PV64"
            },
            "m2.2xlarge": {
                "Arch": "PV64"
            },
            "m2.4xlarge": {
                "Arch": "PV64"
            },
            "m3.medium": {
                "Arch": "HVM64"
            },
            "m3.large": {
                "Arch": "HVM64"
            },
            "m3.xlarge": {
                "Arch": "HVM64"
            },
            "m3.2xlarge": {
                "Arch": "HVM64"
            },
            "c1.medium": {
                "Arch": "PV64"
            },
            "c1.xlarge": {
                "Arch": "PV64"
            },
            "c3.large": {
                "Arch": "HVM64"
            },
            "c3.xlarge": {
                "Arch": "HVM64"
            },
            "c3.2xlarge": {
                "Arch": "HVM64"
            },
            "c3.4xlarge": {
                "Arch": "HVM64"
            },
            "c3.8xlarge": {
                "Arch": "HVM64"
            },
            "c4.large": {
                "Arch": "HVM64"
            },
            "c4.xlarge": {
                "Arch": "HVM64"
            },
            "c4.2xlarge": {
                "Arch": "HVM64"
            },
            "c4.4xlarge": {
                "Arch": "HVM64"
            },
            "c4.8xlarge": {
                "Arch": "HVM64"
            },
            "g2.2xlarge": {
                "Arch": "HVMG2"
            },
            "r3.large": {
                "Arch": "HVM64"
            },
            "r3.xlarge": {
                "Arch": "HVM64"
            },
            "r3.2xlarge": {
                "Arch": "HVM64"
            },
            "r3.4xlarge": {
                "Arch": "HVM64"
            },
            "r3.8xlarge": {
                "Arch": "HVM64"
            },
            "i2.xlarge": {
                "Arch": "HVM64"
            },
            "i2.2xlarge": {
                "Arch": "HVM64"
            },
            "i2.4xlarge": {
                "Arch": "HVM64"
            },
            "i2.8xlarge": {
                "Arch": "HVM64"
            },
            "d2.xlarge": {
                "Arch": "HVM64"
            },
            "d2.2xlarge": {
                "Arch": "HVM64"
            },
            "d2.4xlarge": {
                "Arch": "HVM64"
            },
            "d2.8xlarge": {
                "Arch": "HVM64"
            },
            "hi1.4xlarge": {
                "Arch": "HVM64"
            },
            "hs1.8xlarge": {
                "Arch": "HVM64"
            },
            "cr1.8xlarge": {
                "Arch": "HVM64"
            },
            "cc2.8xlarge": {
                "Arch": "HVM64"
            }
        },
        "AWSRegionArch2AMI": {
            "us-east-1": {
                "PV64": "ami-1ccae774",
                "HVM64": "ami-1ecae776",
                "HVMG2": "ami-8c6b40e4"
            },
            "us-west-2": {
                "PV64": "ami-ff527ecf",
                "HVM64": "ami-e7527ed7",
                "HVMG2": "ami-abbe919b"
            },
            "us-west-1": {
                "PV64": "ami-d514f291",
                "HVM64": "ami-d114f295",
                "HVMG2": "ami-f31ffeb7"
            },
            "eu-west-1": {
                "PV64": "ami-bf0897c8",
                "HVM64": "ami-a10897d6",
                "HVMG2": "ami-d5bc24a2"
            },
            "eu-central-1": {
                "PV64": "ami-ac221fb1",
                "HVM64": "ami-a8221fb5",
                "HVMG2": "ami-7cd2ef61"
            },
            "ap-northeast-1": {
                "PV64": "ami-27f90e27",
                "HVM64": "ami-cbf90ecb",
                "HVMG2": "ami-6318e863"
            },
            "ap-southeast-1": {
                "PV64": "ami-acd9e8fe",
                "HVM64": "ami-68d8e93a",
                "HVMG2": "ami-3807376a"
            },
            "ap-southeast-2": {
                "PV64": "ami-ff9cecc5",
                "HVM64": "ami-fd9cecc7",
                "HVMG2": "ami-89790ab3"
            },
            "sa-east-1": {
                "PV64": "ami-bb2890a6",
                "HVM64": "ami-b52890a8",
                "HVMG2": "NOT_SUPPORTED"
            },
            "cn-north-1": {
                "PV64": "ami-fa39abc3",
                "HVM64": "ami-f239abcb",
                "HVMG2": "NOT_SUPPORTED"
            }
        }
    },
    "Outputs": {
        "URL": {
            "Value": {
                "Fn::Join": [
                    "",
                    [
                        "http://",
                        {
                            "Fn::GetAtt": [
                                "WebServerInstance",
                                "PublicIp"
                            ]
                        }
                    ]
                ]
            },
            "Description": "Newly created application URL"
        }
    }
}

続きを読む

Run CommandとStep Functionsでサーバレス(?)にEC2のバッチを動かす

動機

cronやdigdag-serverは便利で使い勝手がいいですが、常時サーバを動作させておかないといけないのが難点です。一日一回の業務のために、インスタンスを立ち上げっぱなしにしたり冗長構成にするのはコスト的にやめたいなという場合もあると思います。
そこで本記事では、EC2 Run CommandとStep Functionsを使って、EC2インスタンス起動、Dockerを使ったバッチ起動、EC2シャットダウンまでの一連の処理の設定の仕方を説明していこうと思います。

処理実装

State Machine

State Machineの概要は下記になります。基本的には、処理開始→数秒待つ→状態を監視→状態がOKなら次へ進む、という構成になっています。

※ Run Command実行時にたまにエラー(InvalidInstanceId)になります。実行前のWaitの時間を長くするなど対策が必要かもしれないです。

スクリーンショット 2017-06-12 2.53.26.png

下記のJSONをStep Functionsへ登録します。

step-functions.json
{
  "Comment": "Invoke job",
  "StartAt": "StartInstance",
  "States": {
    "StartInstance": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:us-east-1:889476386829:function:qiita-sample-start-instance",
      "Next": "WaitInstanceState"
    },
    "WaitInstanceState": {
      "Type": "Wait",
      "Seconds": 60,
      "Next": "ConfirmInstanceState"
    },
    "ConfirmInstanceState": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:us-east-1:889476386829:function:qiita-sample-confirm-instance-state",
      "Next": "ChoiceInstanceState"
    },
    "ChoiceInstanceState": {
      "Type": "Choice",
      "Default": "FailInstanceState",
      "Choices": [
        {
          "Variable": "$.instance_state",
          "StringEquals": "pending",
          "Next": "WaitInstanceState"
        },
        {
          "Variable": "$.instance_state",
          "StringEquals": "running",
          "Next": "StartJob"
        }
      ]
    },
    "FailInstanceState": {
      "Type": "Fail",
      "Cause": "Failed to launch instance"
    },
    "StartJob": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:us-east-1:889476386829:function:qiita-sample-start-job",
      "Retry": [
        {
          "ErrorEquals": [
            "States.TaskFailed"
          ],
          "IntervalSeconds": 30,
          "MaxAttempts": 3,
          "BackoffRate": 2
        }
      ],
      "Next": "WaitJob"
    },
    "WaitJob": {
      "Type": "Wait",
      "Seconds": 10,
      "Next": "ConfirmJobStatus"
    },
    "ConfirmJobStatus": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:us-east-1:889476386829:function:qiita-sample-confirm-job-status",
      "Next": "ChoiceJobStatus"
    },
    "ChoiceJobStatus": {
      "Type": "Choice",
      "Default": "WaitJob",
      "Choices": [
        {
          "Or": [
            {
              "Variable": "$.job_status",
              "StringEquals": "Failed"
            },
            {
              "Variable": "$.job_status",
              "StringEquals": "TimedOut"
            },
            {
              "Variable": "$.job_status",
              "StringEquals": "Cancelled"
            },
            {
              "Variable": "$.job_status",
              "StringEquals": "Success"
            }
          ],
          "Next": "TerminateInstance"
        }
      ]
    },
    "TerminateInstance": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:us-east-1:889476386829:function:qiita-sample-terminate-instance",
      "End": true
    }
  }
}

Lambda関数

前準備

Lambda関数へ割り当てられているrole(大抵の場合はlambda-default-roleという名前になっている)へ、下記の権限を割り振ってください。

  • ec2:RunInstances
  • iam:PassRole
  • ec2:DescribeInstances
  • ssm:SendCommand
  • ec2:TerminateInstances
  • states:StartExecution

また、同じroleへ下記のポリシーもアタッチしてください。

  • AmazonSSMReadOnlyAccess

さらに、EC2インスタンスにアタッチするIAM roleを作成し、下記ポリシーをアタッチしておいてください。(本記事ではssm-roleと名前をつけます)

  • AmazonSSMFullAccess

EC2インスタンスを起動する

EC2インスタンスを起動します。取得したインスタンスIDを次のLambdaへ渡します。

start_instance.py
import boto3
import base64


def lambda_handler(event, context):
    client = boto3.client('ec2', region_name='us-east-1')
    resp = client.run_instances(
        ImageId='ami-898b1a9f',
        MinCount=1,
        MaxCount=1,
        KeyName='my-key',
        SecurityGroups=['default'],
        UserData=_make_user_data(),
        InstanceType='m3.medium',
        IamInstanceProfile={
            'Name': 'ssm-role',
        },
    )

    event['instance_id'] = resp['Instances'][0]['InstanceId']

    return event

def _make_user_data():
    with open('user-data.txt', 'rb') as f:
        return base64.b64encode(f.read())
user-data.txt
#!/bin/bash -xe

cd /tmp
curl https://amazon-ssm-us-east-1.s3.amazonaws.com/latest/linux_amd64/amazon-ssm-agent.rpm -o amazon-ssm-agent.rpm
yum install -y amazon-ssm-agent.rpm

EC2インスタンスの起動状況を確認する

confirm_instance_state.py
import boto3


def lambda_handler(event, context):
    ec2 = boto3.resource('ec2', region_name='us-east-1')
    instance = ec2.Instance(event['instance_id'])

    event['instance_state'] = instance.state['Name']

    return event

バッチを起動する

EC2 Run CommandでEC2インスタンスへコマンドを発行していきます。本記事では説明のためにpublicなrepositoryからイメージをpullしていますが、本番利用ではprivate repositoryからイメージを利用することが多いと思うので、そのための処理も入れています。
また、dockerの起動オプションで、標準出力をCloudWatch Logsへログ転送するように設定すると便利です(あらかじめLog Groupを作っておく必要があると思います)。

start_job.py
import boto3


def lambda_handler(event, context):
    client = boto3.client('ssm', region_name='us-east-1')

    command = "docker run --log-driver=awslogs" \
              " --log-opt awslogs-group=hello-world" \
              " --log-opt awslogs-region=us-east-1 --rm" \
              " -i alpine:latest /bin/echo 'hello, world'"

    resp = client.send_command(
        InstanceIds=[event['instance_id']],
        DocumentName='AWS-RunShellScript',
        MaxConcurrency='1',
        Parameters={
            'commands': [
                "yum install -y docker",
                "service docker start",
                "eval $(aws ecr get-login --no-include-email --region us-east-1)",
                command,
            ],
        },
        TimeoutSeconds=3600,
    )

    event['command_id'] = resp['Command']['CommandId']

    return event

バッチ処理の状態を確認する

confirm_job_status.py
import boto3


def lambda_handler(event, context):
    client = boto3.client('ssm', region_name='us-east-1')

    resp = client.get_command_invocation(
        CommandId=event['command_id'],
        InstanceId=event['instance_id'],
    )

    event['job_status'] = resp['Status']

    return event

EC2インスタンスをシャットダウンする

terminate_instance.py
import boto3


def lambda_handler(event, context):
    ec2 = boto3.resource('ec2', region_name='us-east-1')
    instance = ec2.Instance(event['instance_id'])
    resp = instance.terminate()

    return event

Step Functionsを呼び出す

下記Lambda関数を定期実行するよう設定します。State Machine名は変わりやすいので環境変数にしておくと便利です。

invoke-step-functions.py
import boto3
import uuid
import os


def lambda_handler(event, context):
    account_id = event.get('account')

    state_machine_arn = 'arn:aws:states:us-east-1:%s:stateMachine:%s'\
                        % (account_id, os.environ.get("STATE_MACHINE_NAME"))
    client = boto3.client('stepfunctions', region_name='us-east-1')
    resp = client.start_execution(
        stateMachineArn=state_machine_arn,
        name=str(uuid.uuid4()),
    )

    return 'invoked step functions'

まとめ

本記事では、EC2 Run CommandとStep Functionsを使って定期的にバッチ処理を起動するやり方を説明しました。今回は簡単な実装にしましたが、スポットインスタンスの価格を調べて10%上乗せして落札できたらそちらを利用するなど、工夫すれば複雑な処理も可能です。
従来のPaaSサービスでは待ち時間もCPU課金されますが、Step Functionsは状態遷移回数での課金ですので新たな使い方ができると思います。

追記

Githubへ公開しました
https://github.com/runtakun/qiita-sample-step-functions

続きを読む

Visual Studio Codeでリモートマシン上のNode.jsファイルを直接操作(編集、デバッグ実行)する

実現すること

Visual Studio Codeでリモートマシン上のNode.jsファイルを直接操作(編集、デバッグ実行)します

    debug.png
    ローカルマシン(Windows)でリモートマシン(Linux)のNode.jsファイルを直接操作

対象者

  • Visual Studio Codeを使ってNode.jsの開発をしている人
  • LinuxマシンのファイルをViとかEmacsで編集するが煩わしいと思っている人
  • ローカルで開発したコードをいちいちリモートのLinuxサーバに転送するのが面倒な人
  • リモートのLinuxサーバのソースを直接デバッグ実行したい人

検証環境

  • ローカルマシン

    • Windows 10
    • Visual Studio Code 1.13.0
  • リモートマシン

    • AWS EC2 (Amazon Linux AMI 2017.03.0.20170417 x86_64 HVM)

リモートマシン側の準備

Node.js

インストール

$ sudo su
# curl -sL https://rpm.nodesource.com/setup_8.x | bash -
# yum install -y gcc-c++ make
# yum install -y nodejs

動作確認

# curl -sL https://deb.nodesource.com/test | bash -
# npm --v
# node -v

参考URL

リンク:https://github.com/nodesource/distributions#enterprise-linux-based-distributions

jmate

インストール

# npm -g install jmate

参考URL

リンク:https://github.com/jrnewell/jmate

SSH

セキュリティグループの設定でインバウンド、アウトバウンドともに52698ポートを許可します。

  • タイプ:カスタムTCPルール
  • プロトコル:TCP
  • ポート範囲:52698
  • ソース:許可したいIPアドレス

クライアントマシン側の準備

Visual Studio Code, Node.js

Visual Studio Code, Node.jsをインストールしておきます。

Windows用のSSHクライアントのインストール

Windowsで実行可能はSSHクライアント(ssh.exe)をインストールしPATHを通します。
SSHクライアントは、 git for windows などに含まれています。

SSH

キーペア

インスタンス生成時に作成したキーペア(*.pemファイル)を ~.ssh に保存します。
ここでは、my-ec2.pem というファイル名で保存したものとして以降説明します。

configファイル

毎回長いsshコマンドをうたなくても大丈夫なようにconfigファイルに設定を書いておきます。

~.sshconfig
ServerAliveInterval 60

Host my-ec2
    HostName ※EC2のIPアドレス
    User ec2-user
    IdentityFile ~/.ssh/my-ec2.pem
    RemoteForward 52698 127.0.0.1:52698

テスト

sshコマンドでサーバにアクセスできることを確認します。

> ssh my-ec2

自動的にログインできればSSHの設定完了です。

Remote VSCode

インストール

Visual Studio Code > EXTENSIONS (Ctrl+Shift+X) > Remote VSCode > Install

settings.jsonの設定

Visual Studio Code > File > Preferences > Settings (Ctrl+Comma)

User Settingsに以下を追記

  //-------- Remote VSCode configuration --------

    // Port number to use for connection.
    "remote.port": 52698,

    // Launch the server on start up.
    "remote.onstartup": true

"remote.onstartup": false の設定にした場合は、F1でcommand palleteを表示し、Remote: Start server でRemote VSCodeのサーバーを起動します。

ローカルのVisual Studio Codeでリモートのファイルを操作

  1. Visual Studio Codeを起動します。
  2. Visual Studio CodeのTerminalでサーバにSSH接続します。

    > ssh my-ec2
    
  3. ファイルオープン
    Visual Studio Codeでリモートのファイルを開きます。

    $ jmate -p 52698 ファイル名.js
    
  4. 編集とデバッグ実行
    4.1. ファイルを編集します。
    4.2. ソースコードにブレークポイントをはります。
    4.3. デバッグウィンドウを開きます。(Ctrl+Shift+D)
    4.4. デバッグ実行します。(F5)

 これで、ファイルの編集、ステップ実行、変数の参照、ウォッチ式の設定、コールスタックの参照、ブレークポイントの設定がローカルファイルと同様に動作するようになりました。

続きを読む