AWS SSM で、EC2に任意のコマンドを実行する

ssm は、ssh で接続することなく、 必要な role があれば、特定の EC2 上へコマンドを送ることが出来ます。
Amazon Linux に対しても、実行できます。

EC2の自動セットアップを行いながらも、ssh キーの登録をしたくないようなときに役立ちます。

実行するリモートサーバー側に ssm-agent をインストールする。

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

実行するリモートサーバー側に role を設定する。

AmazonEC2RoleForSSM

コマンドを送る元に、role を設定する。

AmazonSSMFullAccess

実行方法

こんな感じ

def command(instance, commands):
  ssm = boto3.client('ssm')
  r = ssm.send_command(
    InstanceIds = [instance.instance_id],
    DocumentName = "AWS-RunShellScript",
    Parameters = {
      "commands": commands
    }
  )
  command_id = r['Command']['CommandId']

  while True:
    time.sleep(0.2)
    res = ssm.list_command_invocations(CommandId=command_id)

    invocations = res['CommandInvocations']
    if len(invocations) <= 0: continue

    status = invocations[0]['Status']
    if status == 'Success': return True
    if status == 'Failed': return False

Rails5 では、production 時、config.autoload_paths が機能しない

こちらに変更点が書かれていますが、

A Guide for Upgrading Ruby on Rails — Ruby on Rails Guides

Autoloadを有効にする

$ vi config/environments/production.rb

config.enable_dependency_loading = true

起動時にロードする

config.eager_load_paths += %(hoge)

どちらかの対応で読まれるようになります。

Rails5 ActiveRecord で select をする時の高速化便利関数

select 高速化のヒント

class Parent
  has_many :childs
end

class Child
  belongs_to :parent
end
has_many の配列データ側を絞り込む。なければ parent も返らない。
Parent.includes(:childs).where("childs.age > ?", 20).references(:childs)
belongs_to のデータを事前にロードし、自身を絞り込む
Child.includes(:parent).where(name: "child name")
belongs_to のデータを事前にロードし、自身を絞り込む
Child.includes(:parent).where("parent.name = ?", "parent name")

AWS Elastic Beanstalk x Ruby on Rails で構築する

セットアップする環境

Ruby on Rails5
RDS (Mysql)
Puma
CloudFront
を用いて起動させる。

アプリケーション名は {app name} とする。

手順

RDS を起動

eb コマンドで、同時にRDS を起動させることも可能だが、アプリケーションの削除と同時に、RDSも消えてしまうため、別々に作成する。
マスターユーザー、マスターユーザーのパスワード、インスタンス名は、何でも良く、DBは作らなくて良い。

RDSを作るとき、新規で SecurityGroup を作成し、その名前は rds-launch-wizard に、インプットのソースに、自身を追加する。

f:id:unching-star:20161006082808p:plain

RDS で ユーザーの作成

権限を絞るため、Beanstalk から接続するためのユーザーを作成する。

GRANT ALTER, CREATE, DELETE, DROP, INDEX, SELECT, UPDATE, INSERT ON dbname_dayo.* TO username_dayo@'%' IDENTIFIED BY 'password_dayo';
Rails プロジェクトに設定を追加

1. 通常のRailsのセットアップを行う。

2. ENV を書く

環境構築時に変数として渡したい所は、ENV で指定する。

$ vi config/database.yml
...

production:
  <<: *default
  database: <%= ENV['RDS_DB_NAME'] %>
  username: <%= ENV['RDS_USERNAME'] %>
  password: <%= ENV['RDS_PASSWORD'] %>
  host: <%= ENV['RDS_HOSTNAME'] %>
  port: <%= ENV['RDS_PORT'] %>
.ebextensions

追加の設定を行う。

1. Rails Deploy 時、 rails db:seed が実行されないため、これを実装する。

container_commands:
  seeddb:
    command: 'export HOME=/root; rails db:seed --trace'
    leader_only: true

2. git が install されず、 bundle install に失敗するため、yum install git を追加する。

packages:
  yum:
    git: []

3. Healthcheck の URL を指定する

option_settings:
  - namespace:  aws:elasticbeanstalk:application
    option_name:  Application Healthcheck URL
    value:  /health_check.txt

4. Nginx の pubilc フォルダ設定が誤っているため、修正する

files:
  "/etc/nginx/conf.d/webapp_healthd.conf":
    mode: "000644"
    owner: root
    group: root
    content: |
      upstream my_app {
        server unix:///var/run/puma/my_app.sock;
      }

      log_format healthd '$msec"$uri"'
                      '$status"$request_time"$upstream_response_time"'
                      '$http_x_forwarded_for';

      server {
        listen 80;
        server_name _ localhost; # need to listen to localhost for worker tier

        if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2})") {
          set $year $1;
          set $month $2;
          set $day $3;
          set $hour $4;
        }

        access_log  /var/log/nginx/access.log  main;
        access_log /var/log/nginx/healthd/application.log.$year-$month-$day-$hour healthd;

        root /var/app/current/public;

        location / {
          try_files  $uri @app;

          gzip_static on;
          gzip on;
          expires 60s;
          add_header Cache-Control public;
        }

        location @app {
          proxy_pass http://my_app; # match the name of upstream directive which is defined above
          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

      }

container_commands:
  01_reload_nginx:
    command: "service nginx reload"


5. RDSに繋ぐためのセキュリティーグループを追加する

option_settings:
  - namespace:  aws:autoscaling:launchconfiguration
    option_name:  SecurityGroups
    value:
      - rds-launch-wizard

6. ELB のポートを変更する

デフォルトのままだと、攻撃されやすいため、ポートを変更します。
ポートの追加を行うと、セキュリティーグループも自動で書き換えてくれます。
ここの設定も、追加の設定となるので、デフォルトの80ポートは、無効にします。

option_settings:
  aws:elb:listener:8080:
    ListenerProtocol: HTTP
    InstanceProtocol: HTTP
    InstancePort: 80

  aws:elb:listener:80:
    ListenerEnabled: false


7. コミットする

コミットしなければ、deploy に反映されない。

$ git commit -m 'hoge'
Elastic Beanstalk を作成する。
$ eb create {app name} \
-i t2.medium \
-r ap-northeast-1 \
--vpc.elbpublic \
--scale 2 \
--envvars RACK_ENV=production,SECRET_KEY_BASE=dafb6ff6cb0952c8842fd8c9419d3333ec5054ce7a9edbf8bc0c2abd012fc26ed94d0,S3_ACCESS_KEY_ID=AWDWDWDWDWDWW2A,S3_SECRET_ACCESS_KEY=UW/mKLUWm/fr4fFggs+5r39l5,S3_BUCKET=hoge-data,S3_ASSET_HOST=https://hoge.com/,RDS_DB_NAME=dbname,RDS_HOSTNAME=db.hoge.ap-northeast-1.rds.amazonaws.com,RDS_PORT=3306,RDS_USERNAME=username,RDS_PASSWORD=password
Cloud Front を使用する

beanstalk の ホスト名を Origin に指定するだけで良い。
キャッシュの時間設定は、Rails 側で指定する。

トラブルシューティング

起動に失敗する場合

ログを確認する

$ eb logs {app name}

AWS Lambda で、 python から mysql に接続する

aws lambda で python から mysql が使いたい場合は、

$ pip install mysql-python -t .
$ vi lambda_function.py
...

さらに、`libmysqlclient.so.*` がないと起動できない。

http://www.filewatcher.com/ ここなどで適当なファイルをダウンロードし、同じフォルダに保存すれば、利用できます。

MiniJSON で text の Json.Deserialize が null になる場合

Json のフォーマットがおかしいことが考えられるが、
textは見かけ上問題ない場合がある。

そんな場合によくあるのが、BOM (バイトオーダーマーク)という情報がテキストデータの先頭に含まれている場合。

windows 標準のメモ帳など、UTF-8には、通常はつけるべきでないとされている、BOMがついてしまう。
この BOM が原因で、読み込みエラーが起きている可能性がある。

json ファイル自体を別のエディタで作りなおすか、プログラム側で、先頭の無駄な文字を消すと、読み込めるようになる。

WWW www = new WWW ("https://xxx/sample.json");

yield return www;

string v = www.text;
v = v.Trim();

var o = Json.Deserialize (v);

プログラムに関わるテキストは、メモ帳で開かないようにしましょう。