2015年6月28日日曜日

指定したユーザーで実行したプログラム(su - user -c program)のPIDファイルを作成する


もっとカッコイイ方法はあると思いますが、とりあえずこんな感じで...
# ./sleep.sh /var/run/sleep.pid

sleep.sh


まず、指定ユーザー(sleep)がプログラム(sleep 60)を実行した後に
"/tmp"下にPIDファイルを書き出しています。
(パーミッションの問題で直接"/var/run/"下に書き出せない)

そして、"root"で"/var/run/"下にPIDファイルを移動しています。

ポイントは、プロセスIDを取得する変数"$!"
をバックスラッシュでエスケープ(\$!)しているところでしょうか?

上記コマンド実行後、下記のように"sleep"ユーザーで実行されているのがわかり
# ps aux | grep sleep
sleep     2822  0.0  0.0 107888   620 ?        S    23:11   0:00 sleep 60
root      2825  0.0  0.0 110400   860 pts/0    S+   23:11   0:00 grep sleep
プロセスIDも問題なく書きだされていることが確認できます。
# cat /var/run/sleep.pid 
2822

Monitの状態("unmonitor"など)をリセットする


Monitで下記のように再起動の上限までいってしまい"unmonitor"の状態になってしまい、
それ以降、プロセスが監視されない状態になってしまったときの対策です。
[JST Jun 28 18:08:14] error    : 'sleep' service restarted 5 times within 5 cycles(s) - unmonitor
まあ普通はMonitのコマンドで実施すると思いますが、
# monit status
monit: error connecting to the monit daemon
のようなエラーが出てしまうことがあります。これはMonitのWebのインターフェースが
利用できない状態になってるためのエラーですが、基本的に使ってないので、
今さら有効にするのもどうかなー、と思い、別の方法を探してみました。

ってことで下記を実行することでリセットできます。
# service monit stop
# rm -f /root/.monit.state
# service monit start
※ "/root/.monit.state"はMonitの状態を記録するファイル(デフォルト)です。

すると、再度プロセスの監視が始まり、プロセスが落ちても起動してくれるようになります。

Monitでプロセスを自動復旧させる


よくある話ですが、せっかくなのでInitスクリプトから作ってみました。

まずは対象のプログラムですが、下記のようにバックグラウンドで
スリープするだけのものを用意しました。

/tmp/sleep.sh

スクリプト内でプロセスID($!)を書き出しているところがポイントです。

次に上記のプログラムを起動するInitスクリプトです。
下記のように、それっぽく用意しました。
プログラム起動時にPIDファイルを引数として渡しています。

/etc/init.d/sleep

次の仕様に準じるところまでは作りこんでいません...
Linux Standard Base Core Specification, Generic Part
Chapter 22. System Initialization
22.2. Init Script Actions

最後にMonitの設定ファイルです。上述のPIDファイルを指定しています。

/etc/monit.d/sleep



準備ができたらMonitを再起動します。
# service monit restart
Shutting down monit:                                       [  OK  ]
Starting monit:                                            [  OK  ]
スリープのプロセスは、当然、指定の時間がくると終了するので、
下記のようにログから、Monitがそれを検知して起動していることがわかります。
# tail -f /var/log/monit
...
[JST Jun 28 16:27:48] error    : 'sleep' process is not running
[JST Jun 28 16:27:48] info     : 'sleep' trying to restart
[JST Jun 28 16:27:48] info     : 'sleep' start: /etc/init.d/sleep
[JST Jun 28 16:28:49] info     : 'sleep' process is running with pid 31169

2015年6月26日金曜日

CloudFormationでRDS(MySQL)を作成する(0からの作成とスナップショットからの作成)


最初は0からRDS(MySQL)を作成して、ユーザーやデータベースを作り、
初期データをインポートしてからスナップショットを取り、その後は、
スナップショットからRDSを作成するって場合があると思います。

特にCloudFormationで環境を作成したり削除したりする場合は、
最初に上記の手順を実施するとが多いと思います。


0からRDS(MySQL)を作るCloudFormationを作るテンプレートは、こんな感じです。

ポイントは
 "DeletionPolicy": "Snapshot"
を付けることで、スタックを削除したときにRDSのスナップショットが自動的に
作成されるようにしています。


作成したRDS(MySQL)に対して、下記のようにユーザーとデータベースを作成します。


初期データのインポートは、こんな感じです。


この状態でスタックを削除すると、上述の通りスナップショットが作成されます。


このスナップショットを利用してRDSを作成するテンプレートです。

0からRDS(MySQL)を作成する場合は、テンプレートに
"Engine": "MySQL",
"MasterUsername": "root",
を指定していましたが、スナップショットから作成する場合は、
これらのパラメータのかわりにスナップショットの指定をしています。
"DBSnapshotIdentifier": "xxxxxxxx-snapshot-dbinstance",

2015年6月25日木曜日

自己証明書を作って"AWS CLI"でAWS(IAM)にアップロードする


とりあえずのもだったので、できるだけ簡単にアップロードまで出来るようにしてみました。
("non-interactive"です)


実行するとこんな感じです。
$ openssl req -new -nodes -x509 \
>     -subj '/C=JP/ST=Tokyo/L=Minato-ku/O=cloudpack/CN=test.clp-staging.jp' \
>     -keyout test.key -out test.crt
Generating a 2048 bit RSA private key
...............................+++
...+++
writing new private key to 'test.key'
-----
$ aws iam upload-server-certificate \
>     --server-certificate-name test \
>     --certificate-body file://test.crt \
>     --private-key file://test.key
{
    "ServerCertificateMetadata": {
        "ServerCertificateId": "ASCAJ6BNSQAUEJ5Q3RQZ2", 
        "ServerCertificateName": "test", 
        "Expiration": "2015-07-25T06:12:15Z", 
        "Path": "/", 
        "Arn": "arn:aws:iam::639706874414:server-certificate/test", 
        "UploadDate": "2015-06-25T06:12:16.174Z"
    }
}

2015年6月21日日曜日

ECSで"Private Registry"を利用するCloudFormationのテンプレートを作ってみた


下記のドキュメントの内容をCloudFormationを使って試してみました
Private Registry Authentication
要は"Container Instance"の"/etc/ecs/ecs.config"に
ECS_ENGINE_AUTH_TYPE=docker
ECS_ENGINE_AUTH_DATA={"https://index.docker.io/v1/":{"username":"my_name","password":"my_password","email":"email@example.com"}}
のように認証情報がセットされるようにします。

CloudFormationのテンプレートは、こんな感じです。

「"Container Instance"の"/etc/ecs/ecs.config"に認証情報を設定」は
次のようにUserDataで実現しています。
"UserData": { "Fn::Base64": { "Fn::Join" : [ "\n", [
    "#!/bin/bash",
    "yum -y update",
    "grubby --default-kernel | grep `uname -r` || reboot",
    { "Fn::Join" : [ "", [
        "echo ECS_CLUSTER=",
        { "Ref": "Cluster" },
        " >> /etc/ecs/ecs.config"
    ] ] },
    "echo ECS_ENGINE_AUTH_TYPE=docker >> /etc/ecs/ecs.config",
    { "Fn::Join" : [ "", [
        "echo ECS_ENGINE_AUTH_DATA='{\"https://index.docker.io/v1/\":{\"username\":\"",
        { "Ref": "Username" },
        "\",\"password\":\"",
        { "Ref": "Password" },
        "\",\"email\":\"",
        { "Ref": "Email" },
        "\"}}' >> /etc/ecs/ecs.config"
    ] ] }
] ] } }
今回は下記"Docker Hub"のPublicなイメージとPrivateなイメージを使って
上記のテンプレートでスタックを作ってみました。


まずはパラメータに"Docker Hub"のログイン情報を設定してスタックを作成してみます。


ECSのクラスターができ、サービス登録したタスクが起動していることがわかります。


実際のタスクも下記の通り、プライベートなイメージ(nginx-private)も含めて
問題ない状態になっています。


次にパラメータに"Docker Hub"のログイン情報を設定せずにスタックを作成してみます。


こちらもサービス登録したタスクが起動している状態になりましたが、


タスクの状況を確認すると、プライベートなイメージ(nginx-private)の方は
イメージが見つからないというエラーになっていることがわかります。
(パブリックなイメージ"nginx-public"は問題ありません)

2015年6月20日土曜日

"Route 53"でサブドメインを別の"Hosted Zone"で管理する(サブドメインの委任)


既に"suz-lab.com"は"Route 53"で管理しているのですが、そのサブドメイン
"sub.suz-lab.com"を別の"Hosted Zone"で管理できるようにしてみました。
って言っても要は"Route 53"でサブドメインの委任(NSレコード)してるだけですが...


"sub.suz-lab.com"の"Hosted Zone"は下記のテンプレートを使って
CloudFormationで作成しました。
(ついでにELB作ってALIASでレコード登録もしています)

スタック作成時のパラメータは、こんな感じです。


スタックが作成し終わると、次のように"sub.suz-lab.com"の"Hosted Zone"が
できていることを確認できます。


次のように確認するとELB(www.sub.suz-lab.com)が名前解決できています。
(上記のNSを利用して名前解決)
$ nslookup www.sub.suz-lab.com ns-1329.awsdns-38.org
Server:  ns-1329.awsdns-38.org
Address: 205.251.197.49#53

Name: www.sub.suz-lab.com
Address: 54.66.227.43
当然、まだサブドメインの委任をしてないので、次のように、
普通に名前解決はできません。
$ nslookup www.sub.suz-lab.com
;; Got SERVFAIL reply from xxx.xxx.xxx.xxx, trying next server
Server:  xxx.xxx.xxx.xxx
Address: xxx.xxx.xxx.xxx#53

** server can't find www.sub.suz-lab.com: NXDOMAIN
ということで下記のように"suz-lab.com"の"Hosted Zone"で"sub.suz-lab.com"を
上記の"Hosted Zone"に委任するようにします。

具体的には、"sub.suz-lab.com"のNSタイプのレコードを登録します。
値は、上記の"Hosted Zone"のNSタイプの値(最後にドットは不要)とします。


この状態で再度、普通に名前解決すると、今度は無事、名前解決できました。
$ nslookup www.sub.suz-lab.com
Server:  xxx.xxx.xxx.xxx
Address: xxx.xxx.xxx.xxx#53

Non-authoritative answer:
Name: www.sub.suz-lab.com
Address: 54.66.227.43

2015年6月19日金曜日

CloudFormationで"Container Instance"が"Auto Scaling"で管理されているECSのクラスターを作成してみた



テンプレートは下記の通りです。

以下、ポイントとなります。

▼CloudFormationのスタックが作られるリージョンのECSイメージが勝手に利用されるように

AMI名が"amzn-ami-2015.03.c-amazon-ecs-optimized"のものを
次のように設定しています。
"RegionMapping": {
    "us-east-1": { "ImageId": "ami-5f59ac34" },
    "us-west-2": { "ImageId": "ami-c188b0f1" },
    "eu-west-1": { "ImageId": "ami-3db4ca4a" },
    "ap-northeast-1": { "ImageId": "ami-ca01d8ca" },
    "ap-southeast-2": { "ImageId": "ami-5b5d2661" }
}

▼ ECSのエージェントが動作するためのポリシーをIAMロールに指定

"ecs:*"的な操作を許可してます。
"Action": [
    "ecs:CreateCluster",
    "ecs:DeregisterContainerInstance",
    "ecs:DiscoverPollEndpoint",
    "ecs:Poll",
    "ecs:RegisterContainerInstance",
    "ecs:Submit*"
]

▼ ECSクラスター名を"/etc/ecs/ecs.config"に登録

これやっとかないとECSクラスターに登録されません!
"UserData": { "Fn::Base64": { "Fn::Join" : [ "\n", [
    "#!/bin/bash",
    "yum -y update",
    "grubby --default-kernel | grep `uname -r` || reboot",
    { "Fn::Join" : [ "", [
        "echo ECS_CLUSTER=",
        { "Ref": "Cluster" },
        " >> /etc/ecs/ecs.config"
    ] ] }
] ] } }

CloudFormationのスタック作成時のパラメータはこんな感じです。
(既にVPCやSubnetが存在する前提です)


スタックが作成し終わると、次のような"Auto Scaling"の"Launch Configuration”が
作られていることを確認できます。("User Data"も設定されています)


"Auto Scaling Group"でインスタンスが二つ起動していることも確認できました。


ということで、ECSクラスターに登録されたEC2も二つになっていました。

2015年6月18日木曜日

"Amazon Linux"で"yum update"でしてカーネルがアップデートしてたらリブートさせる


OS起動時は当然、起動しているカーネルのバージョンと
GRUBのデフォルトカーネルのバージョンは下記の通り同一となります。

▼ GRUBのデフォルトカーネルの確認
# grubby --default-kernel
/boot/vmlinuz-3.14.35-28.38.amzn1.x86_64

▼ 起動しているカーネルの確認
# uname -r
3.14.35-28.38.amzn1.x86_64

では、バージョンが異なる場合ですが、次のようにパッケージアップデートした時に
カーネルもアップデートされた場合となります。
# yum -y update
...

この時、GRUBのデフォルトカーネルはアップデートされたものに更新されてますが、
起動しているカーネルは、下記のように古いままとなります。
# grubby --default-kernel
/boot/vmlinuz-3.14.42-31.38.amzn1.x86_64
#
# uname -r
3.14.35-28.38.amzn1.x86_64

ということで、次のワンライナーでGRUBのデフォルトカーネルと
起動しているカーネルに差異がある時、再起動して起動しているカーネルを
GRUBのデフォルトカーネルに揃えることができます。
# grubby --default-kernel | grep `uname -r` || reboot
# 
Broadcast message from ec2-user@ip-10-0-140-242
 (/dev/pts/0) at 17:41 ...

The system is going down for reboot NOW!
Connection to xxx.xxx.xxx.xxx closed by remote host.
Connection to xxx.xxx.xxx.xxx closed.

再起動後は下記のようにカーネルが揃っているので、同じワンライナーを実行しても
再起動することはありません。
# grubby --default-kernel
/boot/vmlinuz-3.14.42-31.38.amzn1.x86_64
#
# uname -r
3.14.42-31.38.amzn1.x86_64
#
# grubby --default-kernel | grep `uname -r` || reboot
/boot/vmlinuz-3.14.42-31.38.amzn1.x86_64

上記のことをユーザーデータに仕込んで"Amazon Linux"を起動してみました。

実際には、こんな感じで起動しています。

起動したら、パッケージをアップデートして、すぐにリブートが勝手に入りますが、
その後は下記の通り、はじめからGRUBのデフォルトカーネルと起動カーネルが
揃った状態になっています。
# grubby --default-kernel
/boot/vmlinuz-3.14.42-31.38.amzn1.x86_64
#
# uname -r
3.14.42-31.38.amzn1.x86_64
#
# grubby --default-kernel | grep `uname -r` || reboot
/boot/vmlinuz-3.14.42-31.38.amzn1.x86_64

2015年6月17日水曜日

RDSのエンドポイントを"Route 53"の"Private DNS"にCNAMEで別名として定義するCloudFormationのテンプレートを作ってみた


下記のテンプレートでスタックを作成すると
  • DBサブネットグループの作成
  • RDSインスタンスの作成
  • "Route 53"の"Hosted Zone"を作成
  • RDSのDNS名を"Route 53"に登録(CNAME)
が実行されます。

※事前にVPC、サブネット、セキュリティグループが作成済みであり、
そのIDをスタック作成時にパラメータとして渡すことを前提としています。

RDS作成時の注意点として、
  • DBサブネットグループはAZが違う二つ以上のサブネットを登録
  • RDSのユーザー名にハイフンが使えない(suz-lab:×, suzlab:◯)
があります。(何度も作成しなおしました...)

"Route 53"の状態をマネジメントコンソールで確認すると、無事、
RDSのエンドポイントのCNAMEである"rds.suzlab.local"が
登録されていることを確認することができます。


実際に上記のVPC内の適当なEC2から"rds.suzlab.local"に下記のように
接続できることも確認できます。
# mysql -h rds.suzlab.local -u suzlab -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 19
Server version: 5.6.22-log MySQL Community Server (GPL)

Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 
ちなみに上記はCentOS7で実行したのですが、"MySQL Client"のインストールは
次のように実施しています。

AnsibleでPacker用のVPCを作ってから"Amazon Linux"ベースのAMIを作成する


下記のスクリプト群で実現してみました。

$ tree ./
./
├── ansible
│   └── packer
│       └── ansible-amazonlinux.yml
├── cloudformation
│   └── packer.json
├── create-ami.sh
└── packer
    └── ansible-amazonlinux.json

次のように実行することで、VPCが作成されたあとにPackerが、そのVPC内にEC2作って、
いろいろとインストールした後、EC2からAMIを作成してくれます。

 ./create-ami.sh

create-ami.sh


必要な変数を設定して、AnsibleのPlaybookを実行しています。

リージョンの指定(自分がいるリージョンを取得)は
"AWS CLI"で最新の"Amazon Linux AMI"(gp2)のIDを取得する
で紹介したものを使っています。

ansible-amazonlinux.yml


最初にCloudFormationでVPCを作っています。
理由としては、僕が扱ってるアカウントがEC2-Classicが使えたり使えなかったり、
デフォルトVPCがあったり無かったり、だったので、
どこでも同じように利用できるためにPacker用のVPCを、まず作ることにしました。

最初は"AWS CLI"で作ってたんですが、CloudFromationが作成し終わってから、
次の処理を実施する場合、別途CloudFormationの状態チェックを行う処理(ループ)を
作り込む必要があり、それが面倒だったのでAnsibleの利用に切替ました。

上記のPlaybookは、CloudFormationの構築が完了した後に、次のタスクが実行されるので、
状態チェックのループを作り込む必要はありません。
また既にCloudFormationのStackがある場合、テンプレートの修正があった場合は
勝手にUpdateになり、ない場合はスキップもしてくれるので、その辺も楽でした。
(これが噂の冪等性ってやつですね!)

そして、下記で紹介した方法で最新の"Amazon Linux AMI"(gp2)のIDを取得して、
"AWS CLI"で最新の"Amazon Linux AMI"(gp2)のIDを取得する
最後に、VPCやらAMIやらの情報をパラメータとしてPackerに渡してAMIを作成しています。

packer.json


こんな感じのVPCを作ってます。
Packerは、このパブリックサブネットの中にEC2を起動します。


ansible-amazonlinux.json


パラメータで渡ってきたVPCやAMIの情報を使ってAMIを作成しています。
"Shell Provisioner"で"Ansible (Local) Provisioner"が利用できる環境を作ってます。


無事、実行されると、下記のようにAMIが作成されてます。

2015年6月16日火曜日

"AWS CLI"で最新の"Amazon Linux AMI"(gp2)のIDを取得する


と言っても、"--filters"オプションで、対象のAMIをnameで絞って、
"--query"オプションで最新のID取得する感じです。

ちなみに出力結果のダブルクォート消すために"--output text"もつけてます。

「EC2のインスタンスメタデータからリージョンを取得」と合わせれば

コマンドを実行するEC2が起動しているリージョンの、
最新の"Amazon Linux AMI"(gp2)のIDを取得することができます。
(どのリージョンでも同一のコマンドで実現できます)

ちなみにAZ(ap-northeast-1a)を取得して、最後の文字をカットして、
それをリージョンとしています。