AWS独学メモ

頑張って学んでいきます。

サービス俯瞰

コンピューティング関連

サービス名 概要
EC2 仮想サーバー
EC2 Container Service Doker(アプリ実行環境構築ツール)運用サービス
EC2 Container Regstry Dokerイメージ保存・共有サービス。
Elastic Beanstalk .NET/PHP/Python/Ruby/Node.jsアプリを自動でAWSにデプロイ。
Lambda クライアントからのリクエスト発生時に任意プログラミング起動。イベント駆動型サービス。
Auto Scaling CPU使用率等、事前決定条件に応じ、EC2インスタンス増減
Elastic Load Balancing トラフィックに応じ、複数EC2インスタンスに負荷分散

ストレージ・コンテンツ配信

サービス名 概要
S3 ファイルサーバ。画像格納したり。
CloudFront コンテンツ配信ネットワーク。利用者から近い場所から効率よく配信
EBS EC2データを保持するストレージ。EC2のHDD,SSDのような役割。
Elastic File System EC2共有ファイルストレージ
Glacier 低価格ストレージ。仕様頻度低いけど長期保存のバックアップ用。
Import / Export Snowball ペタバイト級の大容量転送サービス。
Storage Gateway オンプレミスとAWSを接続

DB関連

サービス名 概要
RDS DB(MySQL/Oracle/SQL Server/PostgreSQL/Aurora)が利用できる
Database Migration Service 最小限停止時間でDBを移行。オンプレミスのDBサーバからの移行等に用いる
DynamoDB NoSQLデータベスサービス構築/運用。
ElastiCache クラウドでのメモり内キャッシュの管理サービス
Redshift ビッグデータを分析

ネットワーク

サービス名 概要
VPC プライベートネットワーク構築サービス。
Direct Connect オンプレミスのネットワークとAWSのVPCネットワークを直接接続。
Route 53 DNS(ドメイン名とIPアドレスを対応)

開発者用ツール

サービス名 概要
CodeCommit プライベートGit
CodeDeploy 開発アプリを実行環境に自動配置
CodePipeline 継続的デリバリ使用したアプリのリリース

開発ツール

サービス名 概要
CloudWatch AWSリソース監視サービス
CloudFormation テンプレート利用したリソースの作成と管理
CloudTrail ユーザアクティビティとAPI使用状況確認
Config リソースのイベントリ変更の追跡
OpsWorks Chef利用し操作の自動化
Service Catalog 標準化製品の作成と使用
Trusted Advisor パフォーマンスとせきゅりてぃの最適化

セキュリティ

サービス名 概要
IAM AWS認証
Directory Service Active Directoryのホスティングと管理
Inspector アプリのセキュリティ分析
CloudHSM 暗号鍵管理の専用ハードウェア
Key Management Service 暗号鍵作成と管理
WAF 攻撃から保護するファイアウォール

分析

サービス名 概要
EMR Hadoopフレームワーク
Data Pipeline オーケストレーションサービス
Kinesis リアルタイムストリーミングデータとの連携
Machine Learning 機械学習
QuickSight 高速ビジネスインテリジェンスサービス

モバイルサービス

サービス名 概要
Mobile Hub モバイルアプリの構築/テスト/監視
API Gateway RESTful APIの構築/管理
Cofnito ユーザID及びアプリデータの同期
Device Farm iOS/Android/FireOSアプリのテスト
Mobile Analytics アプリ分析の収集/表示/エクスポート
Mobile SDK モバイルソフトウェアの開発キット

アプリケーションサービス

サービス名 概要
AppStream ストリーミングサービス
CloudSearch マネージド型検索サービス
Elastic Transcorder メディアと動画変換
SES Eメール送受信
SNS プッシュ通知サービス
SQS メッセージキューサービス
SWF アプリ同士を連携ワークフローサービス

大企業向け

サービス名 概要
WorkSpaces クラウド上仮想デスクトップパソコンサービス
WorkMail セキュリティ保護、企業向けEメール及びカレンダー
WorkDocs ファイル共有サービス

S3について

用語

用語 意味
バケット データの入れ物
オブジェクト 格納ファイル

ステップ

  1. バケット作成
  2. オブジェクト格納

EC2について

用語

用語 意味
EC2 仮想サーバ。オンプレミスのWindowsサーバやUNIXサーバに相当。
インスタンス 1台の仮想サーバ
EBS(Elastic Block Store) サーバのHDDに相当する仮想ディスク
AMI(Amazon Machine Image) サーバにインストールするOSやミドルウェアやアプリのイメージ。新インスタンスを複数生成時、AMIを利用。
yum パッケージ管理システム
scp(secure copy) SSH機能を用いて、安全にファイル転送する

EC2にSSH接続した

参考ページ1
参考ページ2

ミドルウェアをインストール

yum更新
$ sudo yum -y update
httpdインストール
$ sudo yum  install -y httpd
httpd起動
$ sudo service httpd start
httpd自動起動を確認
$ sudo chkconfig --list httpd
httpd自動起動を設定
$ sudo chkconfig  httpd on
$ sudo chkconfig  httpd off

scp(コンテンツをアップロードする)

【現在ここで躓き中!】
→ 突破!!

参考ページ1

HTTPコンテンツをコピー

HTTPコンテンツのコピー

$ sudo cp /home/ec2-user/index.html /var/www/html/

【現在ここで躓き中!】index.htmlへアクセスできない

続きを読む

Amazon API Gateway+Cognito+JavaScriptでちょっと気をつけること

(2017.6.23現在の情報です)
Amazon API Gatewayを、cognito認証で使うときの注意点です。

リソース設定後、ステージから「SDKの生成」を選ぶことができます。
ただ、このSDK、Cognito認証を想定してないっぽいです。
(認証なし=NONEか、AWS_IAMの認証しか想定してない)

Cognitoで認証する場合、こんな感じです。
※すでにサインイン済みの想定です。

sample.js
AWS.config.region = 'ap-northeast-1'; // Region

var poolData = { UserPoolId: 'ap-northeast-1_xxxxxxxx',
              ClientId: 'xxxxxxx'
};

var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);

if (cognitoUser != null) {
    cognitoUser.getSession(function(err, sessresult) {
         if (sessresult) {
             var idToken = sessresult.getIdToken().getJwtToken();

             var apigClient = apigClientFactory.newClient ();

             var params = {};//必要なら設定
             var body = {};//必要なら設定
             var additionalParams = {
                 headers: {
                    Authorization:idToken //ここが大事
                 }
             }

             apigClient.methodName(params, body, additionalParams)
             .then(function(result){
                  //成功
             }).catch( function(result){
                  //失敗
             });
         }

    });
}


ヘッダーにちゃんとIDトークンを入れるってだけですが。。。
わかんなかったので書いておきます。

最初、IAMのほうの設定の問題かなぁ。。。とか思って色々やってたけど、
そっちはとくに関係なさそうでした。
cognitoとても便利だけど、ちょっとまだ追いついてない感じもしました。

README.mdに書いといて欲しいな。

続きを読む

Fresh Install bitnami redmine stack on AWS, then migrate old data and database from old instance.

Fresh Install bitnami redmine stack on AWS, then migrate old data and database from old instance.

目的: Redmine のバージョンアップ と HTTPS化

ですが、新環境の構築手順のみやっていただければ新規構築手順としても使えます。

  • 前回書いた記事から1年ちょっと経過しました。
  • bitnami redmine stack AMI のバージョンも以下のように変化しました。(2017/06/21 現在)
    • Old version 3.2.1
    • New version 3.3.3
  • 前回は GUI でぽちぽち作成しましたが、今回は AWS CLI を中心に進めます。
  • ついでに前回書いてなかった HTTPS化 についても簡単に追記します。
  • 以下の AWS の機能を利用します。
    • Route53: ドメイン名取得、名前解決(DNS)
    • ACM(Amazon Certificate Manager): 証明書発行
    • ELB(Elastic Load Balancer): 本来は複数インスタンスをバランシングする用途に使うものですが今回は EC2 インスタンス 1台 をぶら下げ、ACM で取得した証明書を配布し外部との HTTPS通信 のために利用します。

注意と免責

  • 無料枠でない部分は料金が発生します。
  • データの正常な移行を保証するものではありません。

前提

  • 現環境が正常に動作していること。
  • 新環境を同一リージョン、同一VPC、同一サブネット内に新たにたてます。
    • インスタンス間のデータ転送を SCP で簡易に行いたいと思います。
    • 適宜セキュリティグループを解放してください。
  • aws cli version
% aws --version
aws-cli/1.11.47 Python/2.7.12 Darwin/16.6.0 botocore/1.5.10

段取り

  • 大まかに以下の順序で進めます
  1. Version 3.3.3 の AMI を使って EC2 インスタンスを起動
  2. Version 3.2.1 のデータバックアップ
    • Bitnami Redmine Stack の停止
    • MySQL Dump 取得
  3. Version 3.3.3 へのデータ復元
    • Bitnami Redmine Stack の停止
    • MySQL Dump 復元
    • Bitnami Redmine Stack の開始
  4. 動作確認

参考資料

作業手順

1. Newer Bitnami redmine stack インスタンス作成

以下の条件で作成します。

  • Common conditions

    • AMI: ami-15f98503
    • Type: t2.micro
    • Public IP: あり
  • User defined conditions
    • Region: N.Virginia
    • Subnet: subnet-bd809696
    • Security Group: sg-5b5b8f2a
    • Keypair: aws-n.virginia-default001
    • IAM Role: ec2-001
    • EBS: 20GB

WEB GUI から作るも良し、AWS CLI から作るも良し
以下コマンド実行例

set-env
KEY_NAME=aws-nvirginia-default001.pem
echo $KEY_NAME
check-ami
aws ec2 describe-images \
    --filters "Name=image-id,Values=ami-15f98503"
check-ami-name
aws ec2 describe-images \
    --filters "Name=image-id,Values=ami-15f98503" \
    | jq ".Images[].Name" \
    | grep --color redmine-3.3.3
create-instance
aws ec2 run-instances \
    --image-id ami-15f98503 \
    --count 1 \
    --instance-type t2.micro \
    --key-name aws-n.virginia-default001 \
    --security-group-ids sg-5b5b8f2a \
    --subnet-id subnet-bd809696 \
    --block-device-mappings "[{\"DeviceName\":\"/dev/sda1\",\"Ebs\":{\"VolumeSize\":20,\"DeleteOnTermination\":false}}]" \
    --iam-instance-profile Name=ec2-001 \
    --associate-public-ip-address
set-env
INSTANCE_ID=i-0f8d079eef9e5aeba
echo $INSTANCE_ID
add-name-tag-to-instance
aws ec2 create-tags --resources $INSTANCE_ID \
    --tags Key=Name,Value=redmine-3.3.3

注意書きにもありますが以下に表示される MySQL Database の root パスワードは初期起動時にしか表示されません。
このときに保管しておくか MySQL のお作法にしたがって変更しておいても良いでしょう

check-instance-created
aws ec2 describe-instances --filter "Name=instance-id,Values=$INSTANCE_ID"
wait-running-state
aws ec2 describe-instances \
    --filter "Name=instance-id,Values=$INSTANCE_ID" \
    | jq '.Reservations[].Instances[].State["Name"]'
get-redmine-password
aws ec2 get-console-output \
    --instance-id $INSTANCE_ID \
    | grep "Setting Bitnami application password to"
get-publicip
aws ec2 describe-instances \
    --filter "Name=instance-id,Values=$INSTANCE_ID" \
    | jq '.Reservations[].Instances[].NetworkInterfaces[].Association'
set-env
PUBLIC_IP=54.243.10.66
echo $PUBLIC_IP
site-check
curl -I http://$PUBLIC_IP/
HTTP/1.1 200 OK
(snip)
ssh-connect
ssh -i .ssh/$KEY_NAME bitnami@$PUBLIC_IP
first-login
bitnami@new-version-host:~$ sudo apt-get update -y
bitnami@new-version-host:~$ sudo apt-get upgrade -y
bitnami@new-version-host:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.5 LTS"
bitnami@new-version-host:~$ sudo ./stack/ctlscript.sh status
subversion already running
php-fpm already running
apache already running
mysql already running
bitnami@new-version-host:~$ sudo ./stack/ctlscript.sh help
usage: ./stack/ctlscript.sh help
       ./stack/ctlscript.sh (start|stop|restart|status)
       ./stack/ctlscript.sh (start|stop|restart|status) mysql
       ./stack/ctlscript.sh (start|stop|restart|status) php-fpm
       ./stack/ctlscript.sh (start|stop|restart|status) apache
       ./stack/ctlscript.sh (start|stop|restart|status) subversion

help       - this screen
start      - start the service(s)
stop       - stop  the service(s)
restart    - restart or start the service(s)
status     - show the status of the service(s)

2. 旧バージョンのバックアップ取得

login_to_oldversion
Welcome to Ubuntu 14.04.3 LTS (GNU/Linux 3.13.0-74-generic x86_64)
       ___ _ _                   _
      | _ |_) |_ _ _  __ _ _ __ (_)
      | _ \ |  _| ' \/ _` | '  \| |
      |___/_|\__|_|_|\__,_|_|_|_|_|

  *** Welcome to the Bitnami Redmine 3.2.0-1         ***
  *** Bitnami Wiki:   https://wiki.bitnami.com/      ***
  *** Bitnami Forums: https://community.bitnami.com/ ***
Last login: Sun May 29 07:33:45 2016 from xxx.xxx.xxx.xxx
bitnami@old-version-host:~$
check-status
bitnami@old-version-host:~$ sudo stack/ctlscript.sh status
subversion already running
php-fpm already running
apache already running
mysql already running
stop
bitnami@old-version-host:~$ sudo stack/ctlscript.sh stop
/opt/bitnami/subversion/scripts/ctl.sh : subversion stopped
Syntax OK
/opt/bitnami/apache2/scripts/ctl.sh : httpd stopped
/opt/bitnami/php/scripts/ctl.sh : php-fpm stopped
/opt/bitnami/mysql/scripts/ctl.sh : mysql stopped
start-mysql
bitnami@old-version-host:~$ sudo stack/ctlscript.sh start mysql
170621 10:04:34 mysqld_safe Logging to '/opt/bitnami/mysql/data/mysqld.log'.
170621 10:04:34 mysqld_safe Starting mysqld.bin daemon with databases from /opt/bitnami/mysql/data
/opt/bitnami/mysql/scripts/ctl.sh : mysql  started at port 3306
check-available-filesystem-space
bitnami@old-version-host:~$ df -h /
Filesystem                                              Size  Used Avail Use% Mounted on
/dev/disk/by-uuid/6cdd25df-8610-4f60-9fed-ec03ed643ceb  9.8G  2.7G  6.6G  29% /
load-env-setting
bitnami@old-version-host:~$ . stack/use_redmine
bitnami@old-version-host:~$ echo $BITNAMI_ROOT
/opt/bitnami
dump-mysql
bitnami@old-version-host:~$ mysqldump -u root -p bitnami_redmine > redmine_backup.sql
Enter password:
bitnami@old-version-host:~$ ls -ltrh
  • scp 準備

手元の作業PCから新Redmine環境へssh接続するときに使用した証明書(pem)ファイルを旧Redmine環境にも作成します。

  • 今回は作業PC上で cat で表示させておいて旧環境のコンソール上にコピペしました。
example
bitnami@old-version-host:~$ vi .ssh/aws-nvirginia-default001.pem
bitnami@old-version-host:~$ chmod 600 .ssh/aws-nvirginia-default001.pem
  • ファイル転送
file_transfer
bitnami@old-version-host:~$ scp -i .ssh/aws-nvirginia-default001.pem redmine_backup.sql <new-version-host-ipaddr>:~
  • 新バージョン側にファイルが届いているか確認
check-transfered-files
bitnami@new-version-host:~$ ls -alh redmine*

3. 新バージョンへの復元

stop-stack
bitnami@new-version-host:~$ sudo stack/ctlscript.sh status
subversion already running
php-fpm already running
apache already running
mysql already running

bitnami@new-version-host:~$ sudo stack/ctlscript.sh stop

/opt/bitnami/subversion/scripts/ctl.sh : subversion stopped
Syntax OK
/opt/bitnami/apache2/scripts/ctl.sh : httpd stopped
/opt/bitnami/php/scripts/ctl.sh : php-fpm stopped
/opt/bitnami/mysql/scripts/ctl.sh : mysql stopped
start-mysql
bitnami@new-version-host:~$ sudo stack/ctlscript.sh start mysql
initial-database
bitnami@new-version-host:~$ mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.6.35 MySQL Community Server (GPL)

(snip)

mysql> drop database bitnami_redmine;

mysql> create database bitnami_redmine;

mysql> grant all privileges on bitnami_redmine.* to 'bn_redmine'@'localhost' identified by 'DATAB
ASE_PASSWORD';

mysql> quit
restore-dumpfile
bitnami@new-version-host:~$ mysql -u root -p bitnami_redmine < redmine_backup.sql
Enter password:
bitnami@new-version-host:~$
edit-line-18
bitnami@new-version-host:~$ vi /opt/bitnami/apps/redmine/htdocs/config/database.yml

    18    password: "DATABASE_PASSWORD"
db-migrate
bitnami@new-version-host:~$ cd /opt/bitnami/apps/redmine/htdocs/
bitnami@new-version-host:/opt/bitnami/apps/redmine/htdocs$ ruby bin/rake db:migrate RAILS_ENV=production
bitnami@new-version-host:/opt/bitnami/apps/redmine/htdocs$ ruby bin/rake tmp:cache:clear
bitnami@new-version-host:/opt/bitnami/apps/redmine/htdocs$ ruby bin/rake tmp:sessions:clear
stack-restart
bitnami@new-version-host:/opt/bitnami/apps/redmine/htdocs$ cd
bitnami@new-version-host:~$ sudo stack/ctlscript.sh restart

bitnami@new-version-host:~$ exit
site-check
curl -I http://$PUBLIC_IP/
HTTP/1.1 200 OK
(snip)

ブラウザでも http://$PUBLIC_IP/ でアクセスして旧環境のユーザー名とパスワードでログイン出来ることを確認してください。

この時点で旧環境のインスタンスを停止させておいて良いでしょう。
いらないと判断した時に削除するなりしてください。

4. おまけ HTTPS化

  • 4-1. Route53 でドメイン名を取得してください

    • .net で 年額 11 USドル程度ですので実験用にひとつくらい維持しておくと便利
  • 4-2. Certificate Manager で証明書を取得します
    • コマンドでリクエストしてます
aws acm request-certificate --domain-name redmine.hogefuga.net
check-status-pending
aws acm describe-certificate \
    --certificate-arn "arn:aws:acm:us-east-1:942162428772:certificate/fdf099f9-ced7-4b97-a5dd-f85374d7d112" \
    | jq ".Certificate.Status"
"PENDING_VALIDATION"
  • 4-3. 承認する

    • ドメインに設定しているアドレスにメールが飛んできます
  • 4-4. ステータス確認
check-status-ISSUED
aws acm describe-certificate \
    --certificate-arn "arn:aws:acm:us-east-1:942162428772:certificate/fdf099f9-ced7-4b97-a5dd-f85374d7d112" \
    | jq ".Certificate.Status"
"ISSUED"
  • 4-5. Classic タイプの HTTPS ELB をつくる
example
aws elb create-load-balancer \
    --load-balancer-name redmine-elb1 \
    --listeners "Protocol=HTTPS,LoadBalancerPort=443,InstanceProtocol=HTTP,InstancePort=80,SSLCertificateId=arn:aws:acm:us-east-1:942162428772:certificate/fdf099f9-ced7-4b97-a5dd-f85374d7d112" \
    --availability-zones us-east-1a \
    --security-groups sg-3c90f343
  • 4-6. インスタンスをくっつける
example
aws elb register-instances-with-load-balancer \
    --load-balancer-name redmine-elb1 \
    --instances i-0f8d079eef9e5aeba
  • 4-7. State が InService になるまで待ちます
example
aws elb describe-instance-health \
    --load-balancer-name redmine-elb1
  • 4-8. DNS Name を確認する(4-10. で使います)
example
aws elb describe-load-balancers \
    --load-balancer-name redmine-elb1 \
  | jq ".LoadBalancerDescriptions[].DNSName"
  • 4-9. 今の設定を確認
example
aws route53 list-resource-record-sets \
    --hosted-zone-id Z3UG9LUEGNT0PE | jq .
  • 4-10. 投入用の JSON をつくる
example
vi change-resource-record-sets.json
example
{
  "Comment": "add CNAME for redmine.hogefuga.net",
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "redmine.hogefuga.net",
        "Type":"CNAME",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": <DNSName>
          }
        ]
      }
    }
  ]
}

4-11. 設定投入

example
aws route53 change-resource-record-sets \
    --hosted-zone-id Z3UG9LUEGNT0PE \
    --change-batch file://change-resource-record-sets.json

4-12. 設定確認

example
aws route53 list-resource-record-sets \
    --hosted-zone-id Z3UG9LUEGNT0PE

4-13. ブラウザ確認

https://redmine.hogefuga.net

4-14. EC2 インスタンスのセキュリティグループ再設定

  • グローバルの TCP:80 削除
  • サブネット内の TCP:80 許可
check
aws ec2 describe-security-groups --group-ids sg-b7d983c8
change
aws ec2 modify-instance-attribute \
    --instance-id i-0f8d079eef9e5aeba \
    --groups sg-b7d983c8

4-15. 再度ブラウザからアクセス可能か確認

続きを読む

EC2 インスタンスを Chat Bot で管理する

こないだ Amazon EC2 でディープラーニングできる GPU インスタンス1を作ったのだが、趣味で使うにしてはまぁ料金が高いので常時起動させておくのはもったいなく、使わないときはインスタンスを停止させるようにしている。

しかし使い始めるときと使い終わったときにいちいち AWS 管理コンソールにログインしてインスタンスの起動/停止をするのが面倒だったので、我が家の Slack Bot から EC2 インスタンスを管理できるようにしてみた。

最終的にできたものはこういう感じ。

以下、作り方。

Chat Bot 用の AWS ユーザを作成する

既存のユーザのアクセスキーを利用してもできるが、セキュリティのために専用のユーザを作成して必要な権限のみを付与するのが好ましい。

今回は API を叩くためのユーザなので「プログラムによるアクセス」にチェックを入れる。

ユーザを作成すると、API を叩く際に必要なアクセスキー (AccessKeyId, SecretAccessKey) が生成されるので控えておく。

EC2 を操作できるポリシーを割り当てる

作成したユーザに必要な権限を付与する。

今回やりたいことは

  • EC2 インスタンスの一覧とステータスを取得する
  • EC2 インスタンスを起動させる
  • EC2 インスタンスを停止させる

の3つなので、それに合わせて以下のようなインラインポリシーを割り当てる。

AllowEC2InstanceManagement
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstanceStatus",
                "ec2:StartInstances",
                "ec2:StopInstances"
            ],
            "Resource": "*"
        }
    ]
}

今回はすべてのインスタンスを操作できるようにするために "Resource": "*" としたが、ARN を指定することで「特定のインスタンスのみを操作可能」というような制限をすることも出来る。

Chat Bot から AWS API を叩く

我が家の Chat Bot は Node.js で動作しているので、AWS JavaScript SDK を利用して AWS API を叩く。

AWS JavaScript SDK をインストール

npm からインストールできる。

$ npm install aws-sdk

アクセスキーの設定

AWS SDK を読み込んでアクセスキーを設定する。

const AWS = require('aws-sdk');

// アクセスキーは環境変数から読み込む
AWS.config.update({
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  region: 'ap-northeast-1', // 東京リージョン
});

// EC2 インスタンスを操作するためのオブジェクトを生成
const ec2 = new AWS.EC2();

アクセスキーをハードコートせずに環境変数から読み込んでいるので、Bot を動作させる環境の環境変数 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY にアクセスキーを設定しておく必要がある。

インスタンスの名前をつける

チャットから ec2 gpgpu start のようにインスタンスの名前を指定して起動/停止できるようにしたいので、インスタンス ID と名前を関連付けられるようにしておく。

// 名前から起動/停止できる EC2 インスタンスの一覧
const instances = [
  { id: "i-01315ab8f6f7015e5", name: "GPGPU" },
];

// 名前からインスタンス ID を返す
function getInstanceId(name) {
  const instance = instances.filter(i => i.name.toLowerCase() === name.toLowerCase())[0];
  return instance ? instance.id : null;
}

// インスタンス ID から名前を返す
function getInstanceName(id) {
  const instance = instances.filter(i => i.id === id)[0];
  return instance ? instance.name : null;
}

Bot のアクションを定義する

チャットから Bot にコマンドメッセージが投稿されたときに AWS API を叩くようなアクションを定義する。

以下のコードは自作の Bot フレームワーク2で動くコードだが、インタフェースを Hubot に似せて作っているので Hubot でもだいたい同じようなノリで書けると思う。

module.exports = (bot) => {

  // 指定した EC2 インスタンスを起動する
  bot.respond(/^ec2 ([w_]+) start$/i, (msg) => {
    const id = getInstanceId(msg.match[1]);
    if (!id) return msg.send('知らないインスタンスですね・・・');
    ec2.startInstances({ InstanceIds: [id] }, (err) => {
      if (err) {
        bot.logger.error(err);
        msg.send('インスタンスの起動に失敗しました...');
      } else {
        msg.send('インスタンスを起動しました');
      }
    });
  });

  // 指定した EC2 インスタンスを停止する
  bot.respond(/^ec2 ([w_]+) stop$/i, (msg) => {
    const id = getInstanceId(msg.match[1]);
    if (!id) return msg.send('知らないインスタンスですね・・・');
    ec2.stopInstances({ InstanceIds: [id] }, (err) => {
      if (err) {
        bot.logger.error(err);
        msg.send('インスタンスの停止に失敗しました...');
      } else {
        msg.send('インスタンスを停止しました');
      }
    });
  });

  // すべての EC2 インスタンスの起動状況を取得する
  bot.respond(/^ec2 status$/i, (msg) => {
    const stateIcons = {
      running: 'large_blue_circle',
      terminated: 'red_circle',
      stopped: 'white_circle',
    };
    ec2.describeInstanceStatus({ IncludeAllInstances: true }, (err, data) => {
      if (err) {
        bot.logger.error(err);
        return msg.send('インスタンス情報の取得に失敗しました...');
      }
      const text = data.InstanceStatuses.map((instance) => {
        const id = instance.InstanceId;
        const state = instance.InstanceState.Name;
        const icon = stateIcons[state] || 'large_orange_diamond';
        const name = getInstanceName(id);
        const nameLabel = name ? ` (*${name}*)` : '';
        return `:${icon}: `${id}`${nameLabel} is ${state}`;
      });
      msg.send(text.join('n'));
    });
  });

  // インスタンスが起動中の場合は1時間に1回通知する
  bot.jobs.add('0 0 * * * *', () => {
    ec2.describeInstanceStatus((err, data) => {
      if (err) {
        bot.logger.error(err);
        return bot.send('インスタンス情報の取得に失敗しました...');
      }
      const count = data.InstanceStatuses.length;
      if (count > 0) bot.send(`${count} 台の EC2 インスタンスが起動中です`);
    });
  });

};

今回は start / stop / status の3つのコマンドに加えて、停止し忘れを防ぐために「一時間に一回通知する」機能も実装した。

ec2 オブジェクトのメソッドを使っている箇所が API を叩いている箇所なわけだが、メソッドのオプションなどの詳細は AWS JavaScript SDK のドキュメント3を参照して欲しい。

動かす

Bot をデプロイしてコマンドを投げてみると、こういう感じ。

これで AWS の管理コンソールにログインしなくても EC2 インスタンスを操作できるようになった。便利。

続きを読む

Rails + Capistrano + Unicorn である日 Gemfileが読み込めなくなった話

Railsアプリを Capistrano + Unicorn + nginxでデプロイができるようになって喜んでいたのですが
ある日いつもの bundle exec cap production deployは成功するが結果が反映されなくなった、、、

unicorn.log
I, [2017-06-18T07:25:00.381245 #18202]  INFO -- : executing ["/var/www/myapp/shared/bundle/ruby/2.4.0/bin/unicorn", "-c", "/var/www/myapp/current/config/unicorn/production.rb", "-E", "production", "-D", {11=>#<Kgio::UNIXServer:fd 11>}] (in /var/www/myapp/releases/20170618072435)
I, [2017-06-18T07:25:00.381795 #18202]  INFO -- : forked child re-executing...
/home/suppin/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/bundler-1.15.1/lib/bundler/definition.rb:31:in `build': /var/www/myapp/releases/20170606053023/Gemfile not found (Bundler::GemfileNotFound)

エラーを見に行ったところGemfileがないと言われていました・・・

解決方法

myapp/config/unicorn/production.rb に下記を追記してあげる。
(環境によってはパスが異なるかもしれません)

production.rb
before_exec do |server|
  ENV["BUNDLE_GEMFILE"] = File.join(File.expand_path("../../../../", __FILE__), "current", "Gemfile")
end

その後unicornを再起動

補足

capistranoを使っていて、いつものkill -9コマンドでpidを殺しにいくと自動デプロイするときに怒られる。なのでcurrent/tmp/pid/unicorn.pidとかのファイルを削除してあげる必要がある。

解決方法は
http://unlearned.hatenablog.com/entry/2014/03/10/230954
を参考にさせていただきました。ありがとうございます。

続きを読む

AWSでWebサイトを作りますか。

AWSのEC2でWordPressを使ってサイトを作った備忘録。変な所でハマったので。
EC2上にAmazon Linux AMI乗っけてその上でApache起動させてそこにWordPress乗せるという回りくどいことをやっているのであんまり参考にならないかも……。

AWSへの登録

AWS公式の「まずは無料で登録」から。日本語だからわかりやすい。

使えるクレジットカードが必要なのは要注意。
AU WalletはMastercardなはずなんだけどダメでした。

EC2インスタンスの作成

認証が終わったら、左上の「サービス」をクリック。
使うことの出来るサービス一覧がずらりと並んでます。
その中で左上の「EC2」を選択。
170617.PNG

青い「インスタンスの作成」ボタンを押すとインスタンスが作成できる……のだが、その前に自分が今どこのリージョンを使っているのかを確認する。
これは右上、自分の名前の横に書いてある。
170617-1.PNG
自分の場合はバージニア北部ですね。

AWSでは、作成したインスタンスは「(アカウント名)が(リージョン名)に作ったインスタンス」ではなく「(リージョン名)に保管されている(アカウント名)が作ったインスタンス」みたいな感じで管理されているみたいです。
なので、インスタンスを作ってから右上を弄ってリージョンを変えると見た目上作成したインスタンスが消えます
あくまで見た目上なので、当然リージョンを移動すればまた見えるようになります。よいこのみなさんはわたしみたいに「き、消えた!?」と焦らないようにしてください。
逆に、インスタンスが消えた!?と思ったらまずは自分のいるリージョンを確認しましょう。

この時参考にしたブログ

リージョンを確認したらインスタンスを作っていきます。
とは言っても、画面に表示されるものを読みながらやっていけばできます。
自分はこのあたりを参考にしつつやりました。

この時、下の青いボタンを押すとその後の設定をすっ飛ばしてインスタンスを作ってくれます。
親切なんだか不親切なんだかわからん……。

自分はここでAMIにAmazon Linux AMIを入れてしまいましたが、実はここで直にWordPressが選べるみたいです。左メニューの「AWS Marketplace」をクリックしてから検索窓に「WordPress」と入れて検索してみてね!

最後に公開鍵と秘密鍵のキーペアを作成します。プルダウンから「新しいキーペアの作成」を選び、適当な名前を付けてDL。これは後で必要になるので、どこに保存したか忘れないようにしておきましょう。

セキュリティグループ

無事にインスタンスが作成出来たらインスタンスの管理画面になります。
インスタンスの状態が「Running」になったら(大体一分位?)、SSH接続に必要なセキュリティグループの設定をします。
インスタンスを選択して「説明」タブを見ると、今使っているセキュリティグループがわかります。
これをクリックするとセキュリティグループの設定へ飛べます。

170617-2.PNG

セキュリティグループの設定ページで「インバウンド」タブを選択するとこんな感じ。
最初は「HTTP」しか無いと思うので、「編集」→「ルールの追加」でSSHを作ってやりましょう。
170617-3.PNG

本当はソースをちゃんと指定してあげたほうがいいんですが、使ってる回線が可変IPなので……。
固定IPを持っているよいこのみなさんはちゃんと指定しましょうね。

SSH接続

さっき作った秘密鍵をPuTTYごった煮版内のPuTTYgenから「読込」してやってPuTTY用の秘密鍵ファイルを作ります。
「全てのファイル(*.*)」で探すのを忘れずに。じゃないと見えません。
そして.ppk形式で保存。パスワードはかけておいたほうが良いでしょう。

PuTTYの設定は下記の通り。

フィールド
セッション>ホスト名 ec2-user@(パブリック DNS (IPv4))
セッション>ポート 22
セッション>接続タイプ SSH
接続>SSH>認証>認証のためのプライベートキーファイル (上で作った.ppkファイル)

セッション一覧で適当な名前をつけて「保存」したら「開く」で接続を始めます。
初回はダイアログが出ますが「OK」でスルー。
秘密鍵のパスワードの入力を求められたら入力。
AMIの起動画面が出たら成功です。

この辺の話は公式のガイドページが詳しいです。

Apacheのインストール

インストール。

# sudo yum -y install httpd

起動。

# sudo service httpd start

わーいらくちーん。
この時点でブラウザからhttp://(パブリック DNS (IPv4))を見てみるとテストページが見れます。

WordPressのインストール

まずはWordPressに必要なPHPとMySQLをインストール。

# yum install httpd mysql-server php php-mysql wget

/etc/php.iniを弄ってタイムゾーンの設定をしときます。

# vi /etc/php.ini
 ; Defines the default timezone used by the date functions
 ; http://www.php.net/manual/en/datetime.configuration.php#ini.date.timezone
 date.timezone = "Asia/Tokyo"

ここを参考にしつつMySQLの設定。
このページに沿ってやるとwpという名前のデータベースとwpという名前のユーザが出来上がっています。

WordPress本体を落としてきて解凍。
さらにApacheがWordPressを読み込めるようにしておく。

# cd /tmp
# wget http://ja.wordpress.org/wordpress-4.8-ja.tar.gz
# tar zxvf wordpress-3.5.1-ja.tar.gz 
# cp -r wordpress /var/www/
# chown -R apache.apache /var/www/wordpress

/etc/httpd/conf/httpd.confを弄って、

  • DocumentRootをWordPressにし、
  • DocumentRootのディレクトリに対して.htaccessによる上書きを許可しておく。
# vi /etc/httpd/conf/httpd.conf
 DocumentRoot "/var/www/wordpress"
 (中略)
 <Directory "/var/www/wordpress"> ←ここをちゃんとDocumentRootと同じにしておく
     AllowOverride All

WordPressの設定ファイルを作る。
予めサンプルファイルが用意されているので、それをコピーして……

# cd /var/www/wordpress
# sudo cp wp-config-sample.php wp-config.php

書き換え。

# vi wp-config.php

 // データベース情報を設定します。
 define('DB_NAME', 'wp');
 define('DB_USER', 'wp');
 define('DB_PASSWORD', 'password');
 define('DB_HOST', 'localhost');
 define('DB_CHARSET', 'utf8');

 // 「put your unique phrase here」を 適当な文字列に置き換えます。
 define('AUTH_KEY',         'put your unique phrase here');
 define('SECURE_AUTH_KEY',  'put your unique phrase here');
 define('LOGGED_IN_KEY',    'put your unique phrase here');
 define('NONCE_KEY',        'put your unique phrase here');
 define('AUTH_SALT',        'put your unique phrase here');
 define('SECURE_AUTH_SALT', 'put your unique phrase here');
 define('LOGGED_IN_SALT',   'put your unique phrase here');
 define('NONCE_SALT',       'put your unique phrase here');

自分はここでPuTTYの文字コードをUTF-8にしていなかったため、viでwp-config.phpを開いた瞬間に画面を文字化けしたものが覆うという大惨事になってしまいました。
その時にwp-config.phpが壊れてしまったようで、ファイルの上半分が消失するという事態に……。
バックアップって大事だね!(もう一度cp wp-config-sample.php wp-config.phpしながら)

ここで再びhttp://(パブリック DNS (IPv4))を覗いてみるとWordPressの初期設定画面が開きます。
もしも「データベース接続確立エラー」と出るならwp-config.phpのデータベース情報のどこかが間違ってます。
もしもwp-config.phpの中身が表示されているようならSyntax Errorです。直しましょ。

独自ドメイン設定

サイト自体は完成したので次はドメインとパブリックDNSを紐付けてやります。

これを参考にしてElastic IPを作成、ついでRoute53を設定。Whoisが更新されたのを確認してからドメインをブラウザに入れてみると……。

動かない。何故だ。

正確に言うと、動いてはいるっぽいけどサイトのテキストしか表示されない。
その状態で色々弄っていたら、あることに気づく。

管理画面に遷移しようとすると新たに設定したElastic IPではなく以前のパブリックDNSが出る。

ちょっと調べてみたらこんなページが。
なるほど、DBには前のパブリックDNSしか登録されてないからこうなるのね。
変えてやったら無事動きましたー!よかったー。

さて、あとはサイト本体を作るだけだ……。

続きを読む

Go言語でDynamoDB接続を試みたときの備忘録

前提

最近、Go言語を触り始めた。

GoからDynamoDBに接続するにあたって、 aws-sdk-go/aws/service/dynamodb を使おうとし、ドキュメントを読んでたら結構難しそうだったので、 guregu/dynamo を使うことにした。

また、DynamoDBも触ったことがなかった。

ゴールは、GoでDynamoDBに読み書きすること!

DynamoDBの設定

まず、AWSコンソール画面からDynamoDBのテーブルを作ります。

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

上記の画面で、「テーブル名」を Test とします。

「プライマリーキー」を user_id とします。型は「数値」にします。

他のattributeにかんしては、時間に関する値を想定してますが、実際にGoからItemをPUTするときに足されるので、定義する必要はありません。(そもそも登録もできない。)

(キャメルケースがいいのか、パスカルケースがいいのか等の命名規則もあるかと思いますが、一旦これでいきます。)

これでAWS側の用意は出来ました。

あと、もう一つ大事なこととして
IAMで、DynamoDBにアクセスできる権限をもったユーザーを作成し、

  • アクセスキー
  • シークレットキー

を入手しましょう。

Go言語をかく!

user_dynamo.goという名前でファイルを作成します。

必要なモジュール

以下のリアブラリをimportします。

user_dynamo.go
import (
  "fmt"
  "time"
  "github.com/aws/aws-sdk-go/aws"
  "github.com/aws/aws-sdk-go/aws/session"
  "github.com/aws/aws-sdk-go/aws/credentials"
  "github.com/guregu/dynamo"
)
ライブラリ名 説明
fmt 標準出力に使う。
time 現在時刻を取得するのに使う。
github.com/aws/aws-sdk-go/aws configに関するオブジェクト作成につかう
github.com/aws/aws-sdk-go/aws/session sessionに関するオブジェクト作成につかう
github.com/aws/aws-sdk-go/aws/credentials awsへのアクセス情報に関するオブジェクト作成につかう
github.com/guregu/dynamo github.com/aws/aws-sdk-go/aws/service/dynamodbに対する操作を便利にしてくれるライブラリ

DynamoDBのスキーマ定義

この言い回しが正しいかわかりませんが、DynamoDBのUserテーブルに登録する予定の項目を構造体として定義します。

user_dynamo.go
type User struct {
  UseId int `dynamo:"use_id"`
  CreatTime time.Time `dynamo:"created_time"`
}

inttime.Time のあとのバッククウォートで囲まれた部分はGoで「タグ情報」と呼ばれるものになります。

DynamoDBのUserテーブルにアクセス

user_dynamo.go
func main(){
  c := credentials.NewStaticCredentials("<アクセスキー>", "<シークレットキー>", "") // 最後の引数は[セッショントークン]

  db := dynamo.New(session.New(), &aws.Config{
      Credentials: c,
      Region: aws.String("<region名>"), // "ap-northeast-1"等
  })
  table := db.Table("User")

 // 続く
}

のように書きます。

データをPUTする

user_dynamo.go
func main(){
  // 続き

  u := User{UserId: 3, CreatTime: time.Now().UTC()}
  fmt.Println(u)

  if err := table.Put(u).Run(); err != nil {
    fmt.Println("err")
    panic(err.Error())
  }

上記をまとめると

user_dynamo.go
package main

import (
  "fmt"
  "time"
  "github.com/aws/aws-sdk-go/aws"
  "github.com/aws/aws-sdk-go/aws/session"
  "github.com/aws/aws-sdk-go/aws/credentials"
  "github.com/guregu/dynamo"
)


type User struct {
  UseId int `dynamo:"use_id"`
  CreatedTime time.Time `dynamo:"created_time"`
}


func main(){
  cred := credentials.NewStaticCredentials("<アクセスキー>", "<シークレットキー>", "") // 最後の引数は[セッショントークン]

  db := dynamo.New(session.New(), &aws.Config{
      Credentials: cred,
      Region: aws.String("<region名>"), // "ap-northeast-1"等
  })

  table := db.Table("User")

  u := User{UserId: 100, CreatedTime: time.Now().UTC()}
  fmt.Println(u)

  if err := table.Put(u).Run(); err != nil {
    fmt.Println("err")
    panic(err.Error())
  }

それではterminalから

$ go run user_dynamo.go

とうってみてください。DynamoDBに

user_id created_time
100 2017-06-02T09:25:56.000147134Z

と登録されたかと思います。これで、DynamoDBへのデータ登録は完了です。

データの読み取り

DynamoDBより

「user_idが100のデータ全て」

データを読み取る場合は以下のようになります。

user_dynamo.go
func main(){
  // 続き
  var users []User
  err := table.Get("user_id", 100).All(&users)
  if err != nil {
    fmt.Println("err")
    panic(err.Error())
  }

  fmt.Println(users) // [{100 2017-06-02T09:25:56.000147134 +0000 UTC}]と出力される。

  for i, _ := range users {
    fmt.Println(users[i]) // {100 2017-06-02T09:25:56.000147134 +0000 UTC}
  }
}

また全データを取得したい場合は

err := table.Get("user_id", 1).All(&users)

というところを

err := table.Scan().All(&users)

とすれば大丈夫です。

さらに created_time によって、絞りたい場合は以下のようにすれば大丈夫です。

err := table.Get("user_id", 1).Filter("created_time >= ?", "2017-06-01 00:00:00").All(&users)

注意点

DynamoDBでは、プライマリーキーは、DBで一つしか存在できないので、本コードでは書き込みをするたびに書き換わります。

参考にしたサイト

http://www.geocities.jp/m_hiroi/golang/abcgo07.html
https://github.com/guregu/dynamo
http://qiita.com/guregu/items/2e9ac305791935b86f5d
https://developers.eure.jp/tech/aws-sdk-go-tutorial-sqs-dynamodb/

続きを読む