Docker Swarm (Swarm Mode) チュートリアルをしてみる

Docker Swarm (Swarm Mode) のチュートリアル Getting started with swarm mode をAWS上でやってみたので日本語でまとめです。

何がうれしいの?

  • ノード (サーバー) を跨いで Dockerサービス のデプロイ/アップデートができます
  • 各ノードへの公開アクセスを Swarm Load Balancer が適切なノードのコンテナにリクエストを振り分けてくれます
    • たとえ コンテナが一つも動いていないノードにアクセス しても、別のノードのコンテナがレスポンスを返してくれます
    • 内部のサービス間のオーケストレーションも Swarm Load Balancer がしてくれている様です
  • サービスの Rolling Update が出来ます
  • (他の似たOSSと比べて) 非常にシンプルで扱いやすいです

ingress-routing-mesh.png

※ docker.com 公式より, Swarm Load Balancer の図。

分からなかったこと

Rollling Update は処理中のリクエストが全て完了するのを待ってからアップデートを開始してくれるか?

AWSで言うところの ELB > Connection Draining の動きになるか?
誰か教えて下さい〜 m(_ _)m

チュートリアルまとめ

Getting started with swarm mode を色々と端折って説明します。

ポートを開ける (Security Groups)

各サーバーのポートを開けます。AWSの Security Groups では以下の様になります。

プロトコル ポート範囲 ソース Remarks
TCP 8080 0.0.0.0/0 Nginxコンテナ用の公開ポート
TCP 22 0.0.0.0/0 SSH接続
TCP 2377 {self-group-id} Swarmクラスタ管理の通信に利用
TCP 7946 {self-group-id} Routing mesh (Swarm Load Balancer)
UDP 7946 {self-group-id} Routing mesh (Swarm Load Balancer)
UDP 4789 {self-group-id} Routing mesh (Swarm Load Balancer)

ノード (サーバー) を起動します

AWS Console から3台のインスタンスを起動します。例えば以下のような感じです。
予めTerminalを3つ開いておき、SSHでログインを済ませておくと後の手順が楽です。

役割 Unix Hostname IP
manager ip-172-30-1-100 172.30.1.100
worker1 ip-172-30-1-200 172.30.1.200
worker2 ip-172-30-1-201 172.30.1.201

Hostname は分かりやすさの為に書いています。手順上重要ではないです

managerノードを起動する

managerノード 172.30.1.100 で以下のコマンドを実行します。

$ docker swarm init --advertise-addr 172.30.1.100

Swarm initialized: current node (udhy6l2u7e2tjo3tstmnz6tiu) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join 
    --token SWMTKN-1-67w211zr8eu6h86cfdy09liwlbc6id4nlxrwr0d8u8fcn1x95u-93n335bihesdnwz6650y2ynmy 
    172.30.1.100:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

workerノードをmanagerノードにjoinする

worker1ノード 172.30.1.200, worker2ノード 172.30.1.201 で以下のコマンドを実行します。

--token ... が毎回違うと思いますので、前手順でConsole出力された物をコピペして下さい

$ docker swarm join 
  --token SWMTKN-1-67w211zr8eu6h86cfdy09liwlbc6id4nlxrwr0d8u8fcn1x95u-93n335bihesdnwz6650y2ynmy 
  172.30.1.100:2377

This node joined a swarm as a worker.

Swarmのノード状態を確認する

以下のコマンドでSwarmのノード状態を確認できます。workerノードからも確認が可能で、今いるノードに * が付いています。

$ docker node ls

ID                           HOSTNAME         STATUS  AVAILABILITY  MANAGER STATUS
udhy6l2u7e2tjo3tstmnz6tiu *  ip-172-30-1-100  Ready   Active        Leader
zsj8brmyynj67bqlw4qhvpgn9    ip-172-30-1-200  Ready   Active
p2jfiueccxcqmtt1wqe4r5kvd    ip-172-30-1-201  Ready   Active

サービスをデプロイする

コマンド docker service create {イメージ} でデプロイします。

$ docker service create 
  --replicas 3 
  --name redis 
  --update-delay 10s 
  redis:3.0.6

q9b5a65fkms8oix50bt487j5c

コマンド docker service ps {サービス名} で確認できます。3つのコンテナがそれぞれ別のノードで動いている事が分かります。

$ docker service ps redis

ID            NAME     IMAGE        NODE             DESIRED STATE  CURRENT STATE               ERROR  PORTS
2jwzoi4i2r3d  redis.1  redis:3.0.6  ip-172-30-1-100  Running        Running about a minute ago         
1a402z122056  redis.2  redis:3.0.6  ip-172-30-1-200  Running        Running about a minute ago         
thhbs8870wnk  redis.3  redis:3.0.6  ip-172-30-1-201  Running        Running about a minute ago 

サービスを Rolling Update する

コマンド docker service update --image {イメージ} {サービス名}Rolling Update をします。

$ docker service update --image redis:3.0.7 redis

redis

古いコンテナが破棄され、新しいコンテナに交換された事が分かります。デフォルトでは1コンテナずつ順にアップデートされます。

$ docker service ps redis

ID            NAME         IMAGE        NODE             DESIRED STATE  CURRENT STATE            ERROR  PORTS
imw1jzgdshta  redis.1      redis:3.0.7  ip-172-30-1-100  Running        Running 24 seconds ago          
2jwzoi4i2r3d   _ redis.1  redis:3.0.6  ip-172-30-1-100  Shutdown       Shutdown 25 seconds ago         
vr5u12jfpe07  redis.2      redis:3.0.7  ip-172-30-1-200  Running        Running 36 seconds ago          
1a402z122056   _ redis.2  redis:3.0.6  ip-172-30-1-200  Shutdown       Shutdown 36 seconds ago         
py37p0ip7mgs  redis.3      redis:3.0.7  ip-172-30-1-201  Running        Running 12 seconds ago          
thhbs8870wnk   _ redis.3  redis:3.0.6  ip-172-30-1-201  Shutdown       Shutdown 13 seconds ago 

サービスを削除する

コマンド docker service rm {サービス名} でサービスを破棄します。

$ docker service rm redis

redis

なくなっています。

$ docker service ps redis

Error: No such service: redis

実際にコンテナを落とすのには数秒時間がかかるので、docker ps でローカルノードの停止処理中のコンテナを確認できます

Routing mesh (Swarm Load Balancer)

Swarmノード上 では Swarm Load Balancer が動いており、サービスの公開ポート (フォワードしたホストOSのポート) へのアクセスは適切なコンテナに自動的に割り振りされます。

ingress-routing-mesh.png

※ docker.com 公式より。図中のIPと本記事中のIPは異なります。ごめんなさい

Nginxサービスを2コンテナで起動します。ホストOSの :8080 を公開ポートに割り当てています。

$ docker service create 
  --name my-web 
  --publish 8080:80 
  --replicas 2 
  nginx

utj6pvxbcw7wjlqhlaylf2wyg

3つのノードのうち、2つのノード (worker1, worker2) にコンテナがデプロイされました。

$ docker service ps my-web

ID            NAME      IMAGE         NODE             DESIRED STATE  CURRENT STATE          ERROR  PORTS
rom8aei8b8ye  my-web.1  nginx:latest  ip-172-30-1-200  Running        Running 7 seconds ago         
5sd5szh49gvg  my-web.2  nginx:latest  ip-172-30-1-201  Running        Running 7 seconds ago 

コンテナがデプロイされたノード (worker1) のNginxに curl でアクセスしてみます。

$ curl 172.30.1.200:8080

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

コンテナが一切動いていないノード (manager) も同じ様にアクセスしてちゃんとレスポンスが返ります!

$ curl 172.30.1.100:8080

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

続きを読む

AWSのEC2インスタンスに複数の端末からアクセスする

Amazon web serviceのEC2へのアクセスには、公開鍵を使います。

公開鍵方式というのは細かいことはさておき、

  • 公開鍵と秘密鍵の2種類の鍵のペアを作成し
  • 公開鍵の方をアクセス先に登録しておき、秘密鍵は自分の端末に保管
  • アクセス時には秘密鍵を用いて認証する

という仕組みです。ですので、秘密鍵はその名の通り秘密です。たとえ個人メールやDropboxなどでも、ネット上である限りあげるべきではないようです(参考)。

AWSでEC2インスタンスを作るときには、この鍵のペアを1組作ります。このうちの公開鍵をサーバーに登録し、アクセスするときには秘密鍵を用います。

$ ssh -i <key-file-path> <ec2 public ip>

たとえばこんな風になります

$ ssh -i my-key.pem ubuntu@12.345.6.789

色々調べたのですが、各EC2インスタンスに対して1つの鍵しか設定できないようです。だとすると、別のユーザーがいたり、1人の作業だとしても複数のPCから使いたい場合にはどうすればいいでしょうか。1つだけしかない秘密鍵を共有するのでしょうか。

実は、EC2インスタンス自体に登録できる鍵は1つですが、それとは別にサーバー上で「ユーザー」を作成し、ユーザーに認証用の公開鍵を設定することができます。すると、「このユーザーに対してログイン」することが可能になります。さらに、ユーザーに管理者権限を与えておけば、ユーザー名義でアクセスしたあとに他のユーザーに切り替えることも可能です。この方法をとれば、各端末で鍵を生成し、公開鍵をユーザーに登録することによって、複数の端末からのアクセスが可能になります。秘密鍵は各端末に固有のものになり、共有する必要はありません。

下記はUbuntuインスタンスで行う場合の手順です(こちらを参考にしました)。

手順

(0)前提

端末1からすでにSSHによるアクセスができており、端末2からもアクセスしたい、と仮定します

(1)端末2において、鍵を生成する

pc2~というのは、端末2上での作業を意味します。pc1~は端末1です。

pc2~ $ ssh-keygen -t rsa -f ~/.ssh/id_rsa_ec2

-t はタイプ指定で、RSA暗号を指定していますが、つけなくてもよいようです。-f は鍵の名前を指定しています。このとき、passphraseを設定すると、鍵を用いてアクセスする際のパスワードとなります。私は面倒だけど一応設定することにしています。
上のコマンドにより、~/.ssh/フォルダ内に、id_rsa_ec2id_rsa_ec2.pubという2つのファイルが作られます。”.pub”は公開鍵、拡張子なしの方は秘密鍵です。

(2)公開鍵を端末1に送る

id_rsa_ec2.pubを端末1に送ります。USBメモリを使ったり、メールに添付したり、クラウドサービスを介するなどして共有します。私はGoogle Driveを使いました。公開鍵なのでネット上に出てしまっても良いはずですが、何となく嫌なのですぐ消しました。

(3)端末1からAWSへ公開鍵を送る

pc1~ $ scp -i mykey.pem id_rsa_ec2.pub ubuntu@111.111.111.111:~/

mykey.pemはもともと持っている秘密鍵ファイル、111.111.111.111の部分はインスタンスの公開IPに読み替えます。こうすることで、インスタンスのホームディレクトリ(/home/ubuntu/)にid_rsa_ec2.pubがコピーされます。

(4)端末1からAWSにアクセス

pc1~ $ ssh -i mykey.pem ubuntu@111.111.111.111

ここからはサーバー内の作業です。

(5)ユーザーを作成する

下記で、コマンドのはじめが $になっているのはユーザーの
立場での作業、#になっているのは管理者の立場での作業です。su -により、管理者にスイッチしています。

ubuntu@~ $ sudo su -
root@~ # adduser tarou

tarouは新しいユーザーの名前で、任意です。このとき、tarouのパスワードを聞かれると思いますので、設定します。パスワードを聞かれなかった場合は、

root@~ # passwd tarou

から設定します。このパスワードは、主にこのユーザーが管理者権限を必要とする処理(sudo ...)を行うときに使います。

つづいて、ユーザーに管理者権限を与えます。管理者権限がないと、別のユーザーへの切り替えができません。

root@~ # echo "test-user ALL=(ALL) ALL" > /etc/sudoers.d/tarou
root@~ # chmod 440 /etc/sudoers.d/test-user

(6)公開鍵を、新しいユーザーに登録する

root@~ # su - tarou
tarou@~ $ mkdir .ssh
tarou@~ $ sudo mv /home/ubuntu/id_rsa_ec2.pub .ssh/authorized_keys
tarou@~ $ sudo chown test-user. .ssh/authorized_keys
tarou@~ $ sudo chmod 600 .ssh/authorized_keys
tarou@~ $ sudo chmod 700 .ssh/

su -tarouにより、ユーザーを切り替えます。tarouの立場から、.sshディレクトリを作り、元のユーザー(ubuntu)のところにある公開鍵を.ssh/authorized_keysへ移動しています(authorized_keysはフォルダではなく公開鍵ファイルそのもの)。
つづいて、ファイルとフォルダのアクセス権限を設定しています。まずファイルのオーナーをtarou自身に設定しています。ファイルの権限には600、つまりオーナーのみが読み書き可能、フォルダには700には、オーナーのみの読み書き実行可能としています。
こうするもののようです(ubuntu側も同じ設定になっています)。

(7)端末2からアクセスできるか実験

以上で設定は終わりです。端末2からtarouユーザーにログインするコマンドは下記のようになります。

pc2~ $ ssh -i .ssh/id_rsa_ec2 tarou@111.111.111.111

認証用の鍵に先ほど作ったものを使い、ログイン先をubuntuではなくtarouに変更しています。うまくいっていれば、これでログインできるはずです。

tarouには管理者権限があるので、ユーザーをubuntuに切り替えることが可能です。そうすると、通常の認証で入ったのと同じ状態になります。

tarou@~ $ sudo su - ubuntu

備考

なお、ここまで勉強して最後に気づいたのは、別のユーザーを立てなくても、デフォルトユーザーであるubuntuの公開鍵に新しいものを追加してしまえば良いのではないかということです。実は、authorized_keysには複数の公開鍵を定義することができます(参考)。

ubuntu~ $ cat new-key.pub >> .ssh/authorized_keys

ですけど、何となくこちらをいじるのは気持ち悪いので、今後も別ユーザーを作成することにし、こちらのautorized_keysには公開鍵を複数持たせることにしています。

続きを読む

AWS ec2 t2タイプには51300コネクションしか繋がらない

この記事ではAWS ec2上のCentOS7にインストールしたGo1.8を使っています。

AWS ec2のt2タイプには51300コネクションしか繋がらないようです。どのドキュメントを見てもこのような仕様は書かれていません。原因は不明でしたが、事実関係を集めたいので、同意非同意によらずに広くコメントいただきたいです。

いろいろ切り分けを繰り返したので、「CentOS7での大規模サーバーチューニングノウハウ」としても役立つ内容になっているかもしれません。

1. テスト環境

ソケットを使ってコネクションをいくつも待ち受けするサーバと、コネクションを多数貼り、貼り終わったら切断するクライアントを作成します。

testserver.go
package main

import (
  "fmt"
  "net"
)

const default_listener string = "0.0.0.0:8000"

func main() {
  listener, err := net.Listen("tcp", default_listener)
  if err != nil {
    fmt.Println("Listen error:", err)
    return
  }
  defer listener.Close()
  fmt.Println("Listening on ", default_listener)

  for {
    conn, err := listener.Accept()
    if err != nil {
      fmt.Println("Accept error:", err)
      break
    }
    go func() {
      defer conn.Close()
      buf := make([]byte, 1024)
      for {
        n, err := conn.Read(buf)
        if n == 0 {
          break
        }
        if err != nil {
          fmt.Println("Read error:", err)
          break
        }
        fmt.Println("Read:", string(buf[:n]))
      }
    } ()
  }
}
testclient.go
package main

import (
  "fmt"
  "net"
  "time"
  "os"
  "strconv"
  "sync"
)

const default_listener string = "172.31.17.55:8000"

func main() {
  // confirm arg
  maxConns := 100
  maxThreads := 100
  if len(os.Args) >= 2 {
    maxConns, _ = strconv.Atoi(os.Args[1])
  }
  if len(os.Args) >= 3 {
    maxThreads, _ = strconv.Atoi(os.Args[2])
  }
  fmt.Println("maxConns :=", maxConns, "maxThreads :=", maxThreads)

  conn := make([]net.Conn, maxConns)
  count := 0
  m := new(sync.Mutex)

  //connect
  var wg sync.WaitGroup
  for val := 0; val < maxThreads; val++ {
    wg.Add(1)
    go func(i int) {
      for j := 0; j <= maxConns / maxThreads; j++ {
        conns := j * maxThreads + i
        if conns >= maxConns {
          break
        }
        var err error
        conn[conns], err = net.Dial("tcp", default_listener)
        if err != nil {
            fmt.Println("Dial error:", err)
            time.Sleep(3600 * time.Second)
            panic(err)
        }
        m.Lock()
        count++    
        if count % 1000 == 0 {
          fmt.Println(count, "Connected.")
        }
        m.Unlock()
      }
      wg.Done()
    } (val)
  }
  wg.Wait()
  if count % 1000 > 0 {
    fmt.Println(count, "Connected.")
  }

  //disconnect
  for i := 0; i < maxConns; i++ {
    conn[i].Close()
    if (i + 1) % 1000 == 0 {
      fmt.Println(i + 1, "Disconnected.")
    }
  }
}

2. 実行結果

t2.micro同士でクライアント〜サーバ構成をとり、同時6万接続を実行させると、51000コネクション接続後に、タイムアウトエラーが発生します。

client
$ go run testclient.go 60000
maxConns := 60000 maxThreads := 100
1000 Connected.
(snip)
51000 Connected.
Dial error: dial tcp 172.31.17.55:8000: getsockopt: connection timed out
(repeat)

このとき、サーバ側でコネクション数を数えると51302でした。

server
$ netstat -tan | grep ':8000 ' | awk '{print $6}' | sort | uniq -c
  51302 ESTABLISHED
      1 LISTEN

なお、必ずしも51302ではなく、試行によって、約51300と言える中で若干数値は変わります。

3. 原因究明のための試行錯誤

3.1. メモリの枯渇ではない

topやvmstatでメモリを調べても余裕がありました。
なお、goでメモリ不足の場合signal: killedと表示してプログラムが終了しますので、このことからもメモリの枯渇では無いことがわかります。

3.2. ファイルディクリプタの枯渇ではない

クライアント、サーバともにファイルディスクリプタは拡張済みです。

$ ulimit -n
120000

なお、わざとファイルディクリプタを小さくすると、以下のようなエラー(サーバーの例)が出ます。このことからもファイルディクリプタの枯渇では無いことがわかります。

Accept error: accept tcp [::]:8000: accept4: too many open files

3.3. TCPポート数の枯渇ではない

クライアント、サーバともにip_local_port_rangeは拡張済みです。

$ sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 1024 65000

なお、わざとip_local_port_rangeを狭くすると、クライアント側に以下のエラーが出ます。このことからもTCPポートの枯渇では無いことがわかります。

Dial error: dial tcp 172.31.17.55:8000: connect: cannot assign requested address

ちなみにサーバー側はip_local_port_rangeをほとんど消費しませんので、狭くしてもエラーは出ません。

3.5. iptablesやfirewalldの影響では無い

iptalbesやfirewalldが動作していれば、セッションテーブル等のバッファ容量の影響も考えられます。しかし、ec2のデフォルトでは、これらは動作していません。

3.6. SELinuxの影響では無い

SELinuxを切ってみましたが、挙動は変わりません。

3.7. 最終セッションの次の接続要求がネットワーク経路上でロストしている

tcpdumpで見てみると、最終セッションの次の接続要求で、クライアント側はSYNを再送してリトライアウトしており、サーバー側ではSYNが届いていません。ネットワーク経路上のどこかでロストしているようにみえます。

なお、このことを観察するには、最終セッションまで接続完了させてその次のセッションでの失敗を見る必要があるため、クライアントのスレッド数を1にするとよいでしょう。

3.8 「満杯」になったインスタンスは新規のネットワーク利用ができなくなる

本状態において、クライアントまたはサーハーのいずれかは「満杯」状態になっており、新規のSSH接続はもちろん、別インスタンスとの間のpingでさえ、双方向で通らなくなります。また、「満杯」ではない側のインスタンスも、SSH接続を1〜数本追加することで「満杯」状態になります。

4. 解消の手段

いろいろ試行錯誤して、インスタンスのシリーズを(クライアント〜サーバーともに)変えることで、解消することがわかりました。どうも、t2シリーズのみの現象のようです。おまけでAzureでもやってみましたが、こちらも事象発生はありません。

インスタンスタイプ 結果
t2.micro 約51300接続で限界
t2.medium 約51300接続で限界
t2.xlarge 約51300接続で限界
m4.large 60000接続完了
m3.medium 60000接続完了
c4.large 60000接続完了
Azure A1 60000接続完了

5. 原因の推定

AWS t2インスタンスタイプに固有の何かが原因のようです。可変ECUの制御が何か副作用をもたらしているのでしょうか・・・? また、事象的には、ネットワーク経路上でのステートフルファイアウォールのセッションテーブルが溢れているようにみえます。セキュリティグループが原因でしょうか・・・?

続きを読む

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へアクセスできない

続きを読む

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. 再度ブラウザからアクセス可能か確認

続きを読む

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しか登録されてないからこうなるのね。
変えてやったら無事動きましたー!よかったー。

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

続きを読む

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ファイルへのパスを渡してあげる。
④デプロイパッケージの構造は、この記事で示したようにする。

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

続きを読む

Mastodonインスタンス構築(鯖:AWS EC2、ドメイン:お名前.com、SSL:Let’s Encrypt)

まずは

QiitaはROM専(死語?)だったので初投稿。
こちらの記事に勇気付けられた。

「僕が書いたことはみんな書いている、ハマっていることは共有しなくてもいい」という考えも浮かぶと思うが…
・情報鮮度の観点で出す価値あり

http://qiita.com/hinom77/items/dfa9e0c734e47271edb7

たしかにググって記事がたくさん出てくると学んでて損はない技術なんだろうと思えてくる。
ビッグウェーブに乗りたいというのもある。
というわけで何番煎じかわからないがマストドンのインスタンス構築記録を書く。
見せ方、引用の仕方など作法があればご容赦。
Qiitaのマークダウンすらままならず…。

基本的な軸

基本的には↓↓を参考にさせていただきました。
私の環境で違ったところだけ横道に反れたりしながら追記してます。
基本は参考URLを見ていただき、たまにこっちに戻ってくるという感じがよいかと。
※以降【】でかこっている中項目は下記参考先の中項目タイトルに準じています。

■マストドンAWS構築チュートリアル完全版|初心者から大規模運用まで 5.お手軽な手順
http://webfood.info/mastodon-aws-tutorial/#section-5

【EC2インスタンスの作成】

インスタンススペックはt2.microを選択。
無料枠で選択できたので。

【Route53でHosted Zoneを作る】

丸々飛ばし。
DNSはお名前.comに任せる。

AWS EC2のインスタンスに固定グローバルIPを付与

AWSでは固定グローバルIP=Elastic IPと呼ばれている。
↓↓に書かれている通りに沿って進める。

■AWS EC2インスタンスにElastic IP(固定グローバルIPアドレス)を割り当てる
https://ac-5.net/aws/aws_elasticip_allocation

AWSのElastic IPを独自ドメインと関連付ける

DNSの設定。関連付ける、という言葉が正しいのかどうか。

■(お名前.com)ネームサーバーのAレコード設定
http://rensrv.com/domain/onamae-com/a_record-setting-onamae-com/

【SSHでログインする】

そのまま。

【Let’s EncryptでSSL証明書を取得する】

$ ./certbot-autoの箇所で途中、エラーで正常終了しなかった。

Requesting root privileges to run certbot...
/home/ubuntu/.local/share/letsencrypt/bin/letsencrypt
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Failed to find executable apache2ctl in PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
Certbot doesn't know how to automatically configure the web server on this system. However, it can still get a certificate for you. Please run "certbot-auto certonly" to do so. You'll need to manually configure your web server to use the resulting certificate.

apache2の起動コマンド(?)が見つからないようだ。
そもそもデフォルトでインストールされていないぽい。
↓↓
●対応:apache2インストール→再度実行

$ sudo apt-get update
$ sudo apt-get install apache2
$ ./certbot-auto

結果としてこれで成功したが、完了までにいろいろ聞かれたので参考までに記載。

●ドメイン名を入力してくれー
No names were found in your configuration files. Please enter in your domain
name(s) (comma and/or space separated) (Enter ‘c’ to cancel):

自前のドメイン名を入力して、エンター。

●HTTPSのみの接続にする?
Please choose whether HTTPS access is required or optional.
——————————————————————————-
1: Easy – Allow both HTTP and HTTPS access to these sites
2: Secure – Make all requests redirect to secure HTTPS access
——————————————————————————-
Select the appropriate number [1-2] then enter:

2の方が無難かと考え、2を入力してエンター。

●成功の確認
——————————————————————————-
Congratulations! You have successfully enabled https://(設定したドメイン)

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=(設定したドメイン)
——————————————————————————-

一応言われている通りにアクセスしたら、A評価(多少時間かかる)。

【ミドルウェアの設定】

t2.microなのでスワップ設定は飛ばし。

と思ったら後ほど出てくるdocker-composeでひっかかった。
処理が途中で止まってしまうのだが、どうやらメモリ不足が原因らしい…。

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

http://uyamazak.hatenablog.com/entry/2017/05/22/151210

たしかにスワップの設定後、再トライしたら処理が完了できた。

あと、念のためnginxの編集前の設定ファイルをコピーして残しておく。

cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.ort

【マストドンのセットアップ】

ここでだいぶ時間を食った…。

Docker動いてない?

ERROR: Couldn’t connect to Docker daemon at http+docker://localunixsocket – is it running?

●いろいろ対応:
・鯖再起動←たぶん関係ない。
・pip3のupgrade←アップグレードは成功したがたぶん関係ない。
 ただ、一応やったことなので残しておく。

$sudo pip3 install --upgrade pip

・インストール済かの確認

$pip3 list
docker-compose (1.13.0)

結局よくわからず↓↓を参考に入れ直す。

■今何かと話題のマストドン(mastodon)鯖を自分用に無料で立てる方法
【必要なものをインストールする】のセクション
http://jtwp470.hatenablog.jp/entry/2017/04/15/174036

その後、再度実行→成功!

$ sudo docker-compose build

以降、

$ sudo docker-compose run --rm web rake secret

から続行。
以降、assetsのdocker-composeで躓くも何とかAbout画面表示までこぎ着けた…。
まずは一旦の達成感。

【メールの設定】

今回はとりあえず自分のテスト用鯖なのでSESの制限は解除しない。
他はそのまま。

【cronの設定】

今回参照させていただいているところはマストドンのインストールディレクトリを指定しているのでcron内のパスを修正。
/home/ubuntu/mastodon→/home/ubuntu/live
他はそのまま。

ログインと管理画面

最後に、今回はとりあえず個人用インスタンスということにしているのでユーザ認証は手動で実行。
ということで↓↓を参考にさせていただきました。

■今何かと話題のマストドン(mastodon)鯖を自分用に無料で立てる方法
【初期登録と管理画面をだす方法】【管理画面の出し方】
http://jtwp470.hatenablog.jp/entry/2017/04/15/174036

なぜか手動認証のコマンドを打っても自前のgmailアカウントが見つからないと言われたので、適当な捨てアドを作り、登録し、管理画面から認証するという手順を踏みました。

さいごに

最初は探り探りだったので時間としては朝から晩までかかりました。
2回目はこの手順を残しておいたので2時間強くらいでできました。
1回目構築終わったあと、疲れなのか何なのかAWSのインスタンを消去してしまい、朝起きたときは絶望しましたが逆にこの手順が間違ってなかったことを自分で証明できてよかったよかった(?)
インスタンス構築にあたり最近の技術も勉強できたのでそれもよかった。
Dockerのことがまだはっきりと理解しきれていないので引き続き勉強ですね。

最後になりましたが、参考にさせていただいた先人たちには多大なる感謝を。
またこの記事がこれからの誰かの役に立てれば幸いです。

参考サイトまとめ

■マストドンAWS構築チュートリアル完全版|初心者から大規模運用まで
http://webfood.info/mastodon-aws-tutorial/
■AWS EC2インスタンスにElastic IP(固定グローバルIPアドレス)を割り当てる
https://ac-5.net/aws/aws_elasticip_allocation
■(お名前.com)ネームサーバーのAレコード設定
http://rensrv.com/domain/onamae-com/a_record-setting-onamae-com/
■今何かと話題のマストドン(mastodon)鯖を自分用に無料で立てる方法
http://jtwp470.hatenablog.jp/entry/2017/04/15/174036

続きを読む

[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"
        }
    }
}

続きを読む