set setting reset

脂肪と糖にはたらくやつ

boto3 で S3 オブジェクトのコピー

boto3 で S3 の操作メモ

バケットに接続

import boto3

s3 = boto3.resource('s3')
bucket_name = "my-bucket"

bucket = s3.Bucket(bucket_name)

prefix の文字列で bucket 内のオブジェクトをフィルタ

prefix = 'myfolder/original.txt'
bucket.objects.filter(Prefix=prefix)

# => s3.ObjectSummary(bucket_name='my-bucket', key=u'myfolder/original.txt')

同一バケット内でオブジェクトのコピー

"""
CopySource は Dict 型で渡す
"""
s3.Object(bucket_name, new_file).copy_from(CopySource={'Bucket': bucket_name, 'Key': original_file_name})

オブジェクトの削除

s3.Object(bucket_name, original_file_name).delete()

フィルタしたオブジェクトを別フォルダにコピーして削除

試していませんが、別のバケットへのコピーでも動きそうです。

import boto3
import time

s3 = boto3.resource('s3')
bucket_name = 'my-bucket'
bucket = s3.Bucket(bucket_name)

new_folder = "new"
old_folder = "old"

for obj in bucket.objects.filter(Prefix=old_folder):
    old_file = obj.key
    object_name = obj.key.split("/")[-1]
    new_file = '%s/%s' % (new_folder, object_name[-1])
    # コピーが成功するまで5回繰り返す
    for i in xrange(1,6):
      res = s3.Object(bucket.name, new_file).copy_from(CopySource={'Bucket': bucket.name, 'Key': old_file})
      if res['ResponseMetadata']['HTTPStatusCode'] == 200:
          break
      else:
          time.sleep(i)
    # 削除が成功するまで5回繰り返す
    for i in xrange(1,6):
      res = obj.delete()
      if res['ResponseMetadata']['HTTPStatusCode'] == 204:
          print "moved %s complete" % new_file
          break
      else:
          time.sleep(i)

オブジェクトをローカルにコピー(ダウンロード)

新たに記事書きました。

rriifftt.hatenablog.com

powershell の連想配列を JSON に変換する

ハッシュを作成します。 @{"Key" = "Value"; } の形式です。

$Members = @{
  "id" = 1;
  "Name" = "hoge";
}

PSObject を生成します

$MessageObject = New-Object -TypeName PSObject

PSObject にメンバーを追加します

foreach ($key in $Members.Keys) {
    Add-Member -InputObject $MessageObject -MemberType NoteProperty -Name $key -Value $Members[$key]
}

JSON にコンバートします

$MessageJson = ConvertTo-Json -Compress $MessageObject

みてみます。

> $MessageJson
{"id":1,"Name":"hoge"}

簡単ですね!

PowerShell再入門:11. PSObjectとは | $m0t0k1x2["code"].content
PowerShell で JSON をファイル入出力 する - tech.guitarrapc.cóm
PowerShell連想配列(ハッシュ) CapmNetwork

はじめての AWS Lambda python で boto3 から ec2 を起動する

はじめての AWS Lambda で boto3 から ec2 を起動する

いまさら感ありありですが表題のことを Management Console からやってみます。

初期画面

Screen Shot 2016-03-03 at 10.43.42.png

bluprint

Get Started で進むとたくさんのサンプルから選ぶことができますが、今回は Skip します

Screen Shot 2016-03-03 at 10.43.54.png

Configure function

設定画面が表示されますので、さっそく Lambda Function を定義していきます。

Screen Shot 2016-03-03 at 10.44.10.png

  • Name
    • Lambda Function の名前。
  • Description
    • 説明を記載します。必須ではありません。
  • Runtime
    • 執筆当時は以下のランタイムがありました。

今回は python2.7 にします。 (というか java も node.js もさっぱりです。。)

Lambda function code

デプロイ方法は 3 種類あります。

  • Edit Code inline
    • 画面のエディタに直接記述
  • Upload a .ZIP file
    • zip ファイルを upload
    • cli からやるときは zip でやるとよさそうです
  • Upload a .ZIP from Amazon S3
    • S3 にコードをアップロードしておき、その S3 URL を記載します

今回は Edit Code inline でやってみます。
test タグが Truestoppedインスタンスを起動するという単純なスクリプトを用意しました。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import boto3

ec2 = boto3.resource('ec2')


def get_specified_tagged_instance_ids(target_tag):
    """
    get target tagged instance-id
    """
    try:
        instances = ec2.instances.filter(
            Filters=[
                {'Name': 'tag:%s' % target_tag, 'Values': ['True']},
                {'Name': 'instance-state-name', 'Values': ['stopped']}])
    except Exception, e:
        print(e)
        sys.exit(2)

    if not instances is None:
        i = []
        for instance in instances:
            i.append(instance.instance_id)
    else:
        print("not found target tagged instances")
        sys.exit(0)

    return i


def start_instances(target_tag):
    """
    start instances
    """
    try:
        instances = get_specified_tagged_instance_ids(target_tag)
    except Exception, e:
        print(e)
        sys.exit(2)

    for instance in instances:
        res = ec2.instances.filter(InstanceIds=[instance]).start()
        status_code = res[0]["ResponseMetadata"]["HTTPStatusCode"]
        if status_code == 200:
            print("operation complete. target -> %s" % instance)
        else:
            print("operation failed. target -> %s" % instance)
            sys.exit(1)


def lambda_handler(event, context):
    target_tag = "test"
    start_instances(target_tag)
    sys.exit(0)

lambda_hanlder()__main__ 的に使うようにしてみました。 なお、lambda_hanlder() という名前は好きなものに変えることができます。

Lambda function handler and role

  • Handler
    • デフォルトは lambda_function.lambda_handler
    • 例えば test.py の hello_world() を実行したい場合は test.hello_world とすることができます
  • Role
    • 適切な IAM Role を付与します。

Role について、今回は basic vpc から作成してみます。 デフォルトの policy は以下のようなものでした。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:CreateNetworkInterface",
        "ec2:DescribeNetworkInterfaces",
        "ec2:DetachNetworkInterface",
        "ec2:DeleteNetworkInterface"
      ],
      "Resource": "*"
    }
  ]
}

ここに ec2:DescribeInstancesec2:StartInstances を追加して保存します。

Advanced settings

  • Memory
    • 128MB から 1536MB まで選択できます
  • Timeout
    • 5min まで設定できます
  • VPC
    • VPC を選択すると、lambda が起動する Subnet / Secrurity Groupを指定できます

確認画面

こんな感じになりました。 Create function に進みます。

Screen Shot 2016-03-03 at 11.31.42.png

Function の定義

Screen Shot 2016-03-03 at 11.33.43.png

今回は一日一回実行することにしますので Event Source のみ使います。

Screen Shot 2016-03-03 at 11.58.01.png

Add event source から CloudWatch Events Schedule を選択します。

Screen Shot 2016-03-03 at 11.59.30.png

  • Rule Name
    • 名前をつけます
  • Rule Description
    • 説明を記載します
  • Schedule Expression
    • rate (*) は繰り返し実行する時に使います
    • cron も書けますのでとても便利です
      • cron の時間は UTC のみです

指定したら submit します。Enable now の場合、少し待つと lambda が起動します。 Monitoring タブと CloudWatch Logs で稼働状況を確認できます。

CloudWatch Logs

Monitoring タブの View logs in CloudWatch からこの function の log stream に移動できます。
print 文の内容が表示されていますね。

Screen Shot 2016-03-03 at 12.22.02.png

awscli

一連の流れを awscli で行うとしたら以下の様な感じかと。

  • コードを zip にする
  • iam role を作成しておく(手抜き)
  • function を作成する
    • zip ファイルなので file:// ではなく、 fileb:// なことに注意
aws lambda create-function \
  --function-name auto_start_test \
  --runtime python2.7 \
  --role arn:aws:iam::**********:role/lambda_basic_vpc_execution \
  --handler auto_start.lambda_handler \
  --zip-file fileb://auto_start.zip \
  --timeout 30 \
  --memory-size 128 \
  --vpc-config SubnetIds=subnet-*******,SecurityGroupIds=sg-****** \
  --region ap-northeast-1
  • event を作成する
aws --profile lk-staging events put-rule \
  --name test \
  --schedule-expression "rate(5 minutes)" \
  --state ENABLED

{
    "RuleArn": "arn:aws:events:ap-northeast-1:**********:rule/test"
}

ただ、function と event の関連付ける方法がわかりませんでした。。。
ご存知の方、いらっしゃれば教えて下さい!

CentOS7 on vagrant に docker をインストールして nginx で hello world するまで

CentOS7 (vagrant) に docker をインストールして nginx で hello world するまでの記録です。 CentOS7 が vagrant up されていることが前提です。

docker インストール

公式 にあったワンライナーでインストール後、起動します。
なお、このワンライナー/etc/yum.repos.d/docker.repo を自動で作成してくれます。

$ curl -fsSL https://get.docker.com/ | sh
$ systemctl start docker.service

↓ インストール中のログ

[vagrant@localhost ~]$ curl -fsSL https://get.docker.com/ | sh
+ sudo -E sh -c 'sleep 3; yum -y -q install docker-engine'
Delta RPMs disabled because /usr/bin/applydeltarpm not installed.
warning: /var/cache/yum/x86_64/7/docker-main-repo/packages/docker-engine-selinux-1.10.0-1.el7.centos.noarch.rpm: Header V4 RSA/SHA512 Signature, key ID 2c52609d: NOKEY
docker-engine-selinux-1.10.0-1.el7.centos.noarch.rpm の公開鍵がインストールされていません
Importing GPG key 0x2C52609D:
 Userid     : "Docker Release Tool (releasedocker) <docker@docker.com>"
 Fingerprint: 5811 8e89 f3a9 1289 7c07 0adb f762 2157 2c52 609d
 From       : https://yum.dockerproject.org/gpg
Created symlink from /etc/systemd/system/sysinit.target.wants/lvm2-lvmpolld.socket to /usr/lib/systemd/system/lvm2-lvmpolld.socket.

If you would like to use Docker as a non-root user, you should now consider
adding your user to the "docker" group with something like:

  sudo usermod -aG docker vagrant

Remember that you will have to log out and back in for this to take effect!

非 root ユーザで docker コマンドを使うにはコマンドを叩くように言われているので、ついでに実行しておきます。

$ sudo usermod -aG docker vagrant

docker のバージョンは以下のようになりました。

[vagrant@localhost docker]$ docker version
Client:
 Version:      1.10.0
 API version:  1.22
 Go version:   go1.5.3
 Git commit:   590d5108
 Built:        Thu Feb  4 18:34:50 2016
 OS/Arch:      linux/amd64
Cannot connect to the Docker daemon. Is the docker daemon running on this host?

Dockerfile の作成

こちらを参考にしながら、適当なディレクトリを作り、Dockerfile を作成します。

[vagrant@localhost docker]$ tree
.
├── Dockerfile
└── index.html

0 directories, 2 files

Dockerfile の中身はこんな感じです。

FROM centos
MAINTAINER hoge hoge@hoge.hoge
RUN yum install -y epel-release
RUN yum install -y nginx
ADD index.html /usr/share/nginx/html/

EXPOSE 80
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;", "-c", "/etc/nginx/nginx.conf"]

GPGキーでWARNING出ましたが、ここでは一旦無視。

  • FROM はイメージ名。ローカルになければ dockerhub から取ってくる
  • MAINTAINER はメンテナの名前とメールアドレスを記入
  • RUN はイメージ起動後に実行するコマンド
  • ADD はファイルを指定したパスに配置
  • EXPOSE は Listen するポート
  • ENTRYPOINT は docker run 時に実行するコマンド

index.html には hello world とだけ書いておきました。

docker build

build します。

$ sudo docker build -t centos/nginx:1.0 .
  • -t は image にタグを打つオプション
    • centos/nginx は REPOSITORY 名
    • :1.0 は TAG 名
  • 最後の引数に Dockerfile のあるディレクトリを指定

build が成功すると image ができあがります。

[vagrant@localhost docker]$ sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos/nginx        1.1                 80d766bb9338        About an hour ago   356 MB
centos/nginx        1.0                 d1345ae2d9a6        9 hours ago         356 MB
centos              latest              61b442687d68        6 weeks ago         196.6 MB
hello-world         latest              690ed74de00f        3 months ago        960 B

docker 実行

build したイメージを実行します。

$ sudo docker run -d -p 80:80 centos/nginx:1.1
753d92d2bf360a529e881efaee564f86840e8d2d649f20825a7132093b45683e
  • -d は docker をバックグラウンド実行
  • -p はポートフォワード設定。 ローカル側:docker側 の順番
  • 実行後の標準出力はコンテナID
 sudo docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
753d92d2bf36        centos/nginx:1.1    "/usr/sbin/nginx -g '"   3 minutes ago       Up 3 minutes        0.0.0.0:80->80/tcp   determined_tesla

コンテナの停止、削除はこのコンテナIDを利用します。

コンテナの中に入ってシェル操作するには以下のオプションで docker run します。

$ sudo docker run -it --entrypoint "/bin/bash" centos/nginx:1.2
[root@920ea5c72a47 /]# 
  • --entrypoint を指定することで Dockerfile の ENTRYPOINT を上書き

起動中のコンテナに入るには以下のようにします。

$ sudo docker exec -i -t *コンテナID* /bin/bash

確認

これで nginx が起動したので、index.html が見えるはずです。

$ curl -s localhost
hello world

見えました。
ブラウザから確認する場合は vagrant の ip アドレスを指定するとよいでしょう。

Screenshot-3.png

コンテナの停止・削除

コンテナを停止するには以下のコマンドを実行します。

$ sudo docker stop *コンテナID*

コンテナを削除するには以下のコマンドを実行します。

$ sudo docker rm *コンテナID*

コンテナが増えると停止がめんどうなので、for 文でぐるぐるしたり。

$ for p in $(sudo docker ps -a -q); do sudo docker rm -f ${p}; done

image の削除

image の削除は以下のコマンドを実行します。

$ sudo docker rmi *イメージID*

コンテナ実行中は削除できないので注意。

以上、ここまでdesu。

参考

参考にさせていただきましたm(__)m

https://docs.docker.com/engine/installation/linux/centos/ http://www.atmarkit.co.jp/ait/articles/1407/08/news031.html http://qiita.com/hihihiroro/items/d7ceaadc9340a4dbeb8f http://heartbeats.jp/hbblog/2014/07/3-tips-for-nginx-on-docker.html http://qiita.com/kasaharu/items/d4654193d75c67d65226 http://qiita.com/hnakamur/items/afddaa3dbe48ad2b8b5c

apache で X-Forwarded-For をログに記録する

記事としてよくありますが、ちょっとハマったので記録として。

<VirtualHost *:80>
    ServerName hoge.com
    DocumentRoot "/var/www/html"
    LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined_x
    CustomLog "/var/log/httpd/access_hoge.com.log" combined_x
    ErrorLog  "/var/log/httpd/error_hoge.com.log"
</VirtualHost>

ELB 配下で使っているため、 %h は除去しました。
また、 combind_x としているのは /etc/httpd/httpd.conf にある LogFormat のデフォルト設定に上書きされてしまうことを回避するためです。

 218 #
 219 # Load config files from the config directory "/etc/httpd/conf.d".
 220 #
 221 Include conf.d/*.conf

 493 #
 494 # The following directives define some format nicknames for use with
 495 # a CustomLog directive (see below).
 496 #
 497 LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

ログは以下のようになります。

52.68.214.11 - - [10/Feb/2016:15:58:36 +0900] "GET /index.html HTTP/1.1" 200 47 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.103 Safari/537.36"

以上です。

ssh で rake spec する時に sudo password が渡せない

追記

serverspec の実行時や、ローカルPCの環境変数に sudo_password を設定すればデフォルトのままで問題ありませんでした。

Sudo password support

If you log into servers as non-root user, Serverspec add "sudo" in front of the command. You can specify sudo password like this.

$ SUDO_PASSWORD=xxxxxxxx rake spec

Or display prompt for sudo password if you run Serverspec like this.

$ ASK_SUDO_PASSWORD=1 rake spec

tutorial を先に読まないとこういうことになりますね。猛省します。

追記ここまで

.ssh/config や /etc/hosts をごにょごにょして、
デフォルトの spec_helper.rb を使って鍵認証でもパスワード認証でも ssh ログインまでできたのですが、sudo できずにエラーで終了してしまいます。

$ rake spec
(in /home/vagrant/serverspec)
/home/vagrant/.rbenv/versions/2.2.3/bin/ruby -I/home/vagrant/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/rspec-support-3.4.0/lib:/home/vagrant/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/rspec-core-3.4.1/lib /home/vagrant/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/rspec-core-3.4.1/exe/rspec --pattern spec/test-001/\*_spec.rb

Package "postgresql-9.3"
hoge@test-001's password:
Wrong sudo password! Please confirm your password on test-001.

Finished in 6.7 seconds (files took 0.37223 seconds to load)
1 example, 0 failures

/home/vagrant/.rbenv/versions/2.2.3/bin/ruby -I/home/vagrant/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/rspec-support-3.4.0/lib:/home/vagrant/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/rspec-core-3.4.1/lib /home/vagrant/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/rspec-core-3.4.1/exe/rspec --pattern spec/test-001/\*_spec.rb failed

必要な環境変数null なことが原因のようです。

if ENV['ASK_SUDO_PASSWORD']
  begin
    require 'highline/import'
  rescue LoadError
    fail "highline is not available. Try Installing it."
  end
  set :sudo_password, ask("Enter sudo password:") { |q| q.echo = failse }
else
  set :sudo_password, ENV['SUDO_PASSWORD']
end

対象は ASK_SUDO_PASSWORDSUDO_PASSWORD の両方の様で、
上記 if 文の後に puts ENV['ASK_SUDO_PASSWORD'] と、 puts ENV['SUDO_PASSWORD'] したら結果は null 。
なので、highline がインストールされているかどうかのチェックもできず落ちていた模様。

(highline が未インストールだったのでパスワード認証の時に、標準入力にパスワード丸見えになってしまった・・)

なので if 文を削って強制的に sudo password を入力させることにしました。
(環境変数を追加するのはちょっと抵抗あるので)
また、ssh が成功した時に set :request_pty, true と書くように怒られたので、追記しています。

require 'serverspec'
require 'net/ssh'

set :backend, :ssh
set :request_pty, true

begin
  require 'highline/import'
rescue LoadError
  fail "highline is not available. Try installing it."
end

set :sudo_password, ask("Enter sudo password: ") { |q| q.echo = false }

host = ENV['TARGET_HOST']

options = Net::SSH::Config.for(host)

options[:user] ||= Etc.getlogin

set :host,        options[:host_name] || host
set :ssh_options, options

これでパスワードでも鍵認証でも実行できました。が、本当にこれでいいんだろうか。。。

apache で elb からのヘルチェックをログに記録しない

apache のバージョン

# httpd -V
Server version: Apache/2.2.15 (Unix)
Server built:   Aug 13 2013 17:29:28
Server's Module Magic Number: 20051115:25
Server loaded:  APR 1.3.9, APR-Util 1.3.9
Compiled using: APR 1.3.9, APR-Util 1.3.9
Architecture:   64-bit
Server MPM:     Prefork
  threaded:     no
    forked:     yes (variable process count)
Server compiled with....
 -D APACHE_MPM_DIR="server/mpm/prefork"
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_SYSVSEM_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=128
 -D HTTPD_ROOT="/etc/httpd"
 -D SUEXEC_BIN="/usr/sbin/suexec"
 -D DEFAULT_PIDLOG="run/httpd.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_LOCKFILE="logs/accept.lock"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="conf/mime.types"
 -D SERVER_CONFIG_FILE="conf/httpd.conf"

ELB-HealthChecker/1.0 からのアクセスログ

***.***.***.*** - - [11/Dec/2015:14:36:42 +0900] "GET /check.html HTTP/1.1" 200 20 "-" "ELB-HealthChecker/1.0" 373 ***.***.***.***

これをログに記録させないようにします。

config

<VirtualHost *:80>
    DocumentRoot /var/www/html
    ServerName hoge.com
 
    SetEnvIf Request_URI "^/check.html$" dontlog
    CustomLog /var/log/httpd/vhost-access.log combined_customized env=!dontlog
    ErrorLog /var/log/httpd/vhost-error.log
</VirtualHost>

CustomLogenv=!dontlog が肝ですね。 反映は reload で OK です。