このエントリーをはてなブックマークに追加

Packer で AMI を作るときにタイムアウトした

Packer で AMI を作るとき、Provisors で時間がかかってくるようになると、次のようなエラーメッセージが表示されて終了することがあります。
今のところ、Ansible + Serverspec を実行しているため、かなり時間がかかってきたようです。。。


...
2016/05/11 07:52:03 packer: 2016/05/11 07:52:03 Allowing 300s to complete (change with AWS_TIMEOUT_SECONDS)
2016/05/11 07:52:24 ui: ==> amazon-ebs: Creating the AMI: foo-2016-05-10T22-45-36Z
2016/05/11 07:52:25 ui: amazon-ebs: AMI: ami-xxx
2016/05/11 07:52:25 ui: ==> amazon-ebs: Waiting for AMI to become ready...
2016/05/11 07:52:25 packer: 2016/05/11 07:52:25 Waiting for state to become: available
2016/05/11 07:52:25 packer: 2016/05/11 07:52:25 Allowing 300s to complete (change with AWS_TIMEOUT_SECONDS)
2016/05/11 07:54:39 ui: ==> amazon-ebs: Modifying attributes on AMI (ami-xxx)...
2016/05/11 07:54:39 ui: amazon-ebs: Modifying: description
...

こんなときは、メッセージのとおり AWS_TIMEOUT_SECONDS 環境変数を設定してから実行するとよくなりました。
また、標準出力に表示されるログがかなり長くなってきたので、ログファイルに出力するようにしました。

今のところ、次のような環境変数を設定しています。
$packer_name というのは AMI 名、$packer_role は AMI のロール名になります。
今のところ、$packer_name の $packer_role ごとに AMI を作っています。


$ export PACKER_LOG=1
$ export PACKER_LOG_PATH="packer-$packer_name-$packer_role-$(date +%Y%m%d%H%M%S).log"
$ export AWS_TIMEOUT_SECONDS=600

Packer でビルドしているとき、途中で終了しても気づけないことがあるので、ログファイルはちゃんとみた方がよいと思います。
CI などで Packer をビルドしているときは、CI のログを見るのが良いかなと思います。

このエントリーをはてなブックマークに追加

Ubuntu 14.04 LTS から 16.04 LTS へのアップグレード方法

待望の Ubuntu 16.04 LSTS がリリースされましたね!
さっそく自サーバの Ubuntu 14.04 LTS Server をアップグレードしてみました。

Digital Ocean に詳しい解説がのっていますが、正式には 16.04.01 でアップグレードが正式に対応になることですので、アップグレードする際には自己責任でお願いします。

まずは、現行のものを最新にアップグレードして、必要なら再起動をしておきます。


$ sudo apt-get update
$ sudp apt-get upgrade

あとは、いつもどおり do-release-upgrade でアップグレードするだけです。ただし、現時点では -d オプションをつける必要があります。


$ sudo apt-get install update-manager-core
$ sudo do-release-upgrade -d

僕の環境では無事アップグレードできたのですが、SSH 接続すると、次のようなエラーになってしまいました。


Unable to negotiate with x.x.x.x: no matching key exchange method found. Their offer: curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha1

これは、~/.ssh/config に、次のような設定が入っていたからです。


KexAlgorithms diffie-hellman-group1-sha1

これを次のように修正しましたところ、接続はできました。


KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256

あとは、16.04 LTS の新機能をチェックしながら、Ubuntu 16.04 LTS を楽しみたいと思います。

また、まだ EC2 上には公式のイメージは提供されていないようですが、楽しみに待っておきたいと思います。

このエントリーをはてなブックマークに追加

Ansible on CentOS 7 ですこしハマった

CentOS 7 上で Ansible 2 系を使っていますが、EPEL にある Ansible パッケージが、現時点で最新の 2.0.2.0 にバージョンアップされていました。
さっそくこのバージョンを使ったところ、次のような JSON をパースするような処理がある場合は、エラーとなってしまいました。


- set_fact: foo_aws_access_key_id="{{ cloudformation_outputs | selectattr('OutputKey', 'equalto', 'FooKey') | map(attribute='OutputValue') | join(',') }}"

これは、取得した CloudFormation の JSON データの OUTPUTS から特にキーの値を取得するものですが、これを実行すると、次のようなエラーになります。


An exception occurred during task execution. To see the full traceback, use -vvv. The error was: TemplateRuntimeError: no test named 'equalto'
fatal: [foo]: FAILED! => {"failed": true, "msg": "Unexpected failure during module execution.", "stdout": ""}

原因を調査したところ、どうも Ansible が使っているテンプレートエンジン Jinja2 が 2.7 系のままだったことが原因みたいです。
たしかに yum 経由でインストールされる Jinja2 が 2.7 系のままでした。


sudo yum info python-jinja2.noarch
読み込んだプラグイン:fastestmirror
Loading mirror speeds from cached hostfile
* base: ftp.osuosl.org
* epel: mirror.symnds.com
* extras: mirror.confluxtech.com
* updates: mirrors.tripadvisor.com
インストール済みパッケージ
名前 : python-jinja2
アーキテクチャー : noarch
バージョン : 2.7.2
リリース : 2.el7
容量 : 3.0 M
リポジトリー : installed
提供元リポジトリー : base
要約 : General purpose template engine
URL : http://jinja.pocoo.org/
ライセンス : BSD
説明 : Jinja2 is a template engine written in pure Python. It provides a
: Django inspired non-XML syntax but supports inline expressions and an
: optional sandboxed environment.
:
: If you have any exposure to other text-based template languages, such
: as Smarty or Django, you should feel right at home with Jinja2. It's
: both designer and developer friendly by sticking to Python's
: principles and adding functionality useful for templating
: environments

ということで、あまりやりたくはないですが、仕方がなく pip コマンドでアップグレードするようにしました。
けっこう Ansible も Jinaja もリリースされるバージョンに問題があることが多いのでバージョンを固定するようにしました。


$ sudo pip install --upgrade Jinja2==2.8

これで、上のような Playobook もちゃんと通るようになりました。

このエントリーをはてなブックマークに追加

はじめての Gatling

Gatling を、とある環境のベンチマークに使ってみたので、入門向けとして「はじめての Gatling」としてまとめてみました。

Gatling は、Scala でベンチマークのシナリオを書くけれど、Scala 力が低いても、かなり手軽にシナリオを記述できるところが便利ですね。

このエントリーをはてなブックマークに追加

はじめての Datadog

本番環境に Datadog を導入し始めていますが、Datadog はじめての方の向けに簡単にスライドとして作成してみました。
Datadog に関するカジュアル会があれば、そこで発表したい内容でしたが、スライドを公開しておきます。

今回は、「はじめての Datadog」ということで Datadog の入門編として作成しましたが、応用編も本番環境へ導入して運用している中で知見がたまったところで公開する予定です。

Datadog すこしクセがあるところもありますけれど、とても便利なモニタリングサービスだと思います。

このエントリーをはてなブックマークに追加

お手軽に Java プログラムを起動できる javactl の紹介

Java プログラムをコンパイルしたあと、jar なファイルを実行するためには java コマンドでいろいろな引数を設定して起動することがよくあります。

そんなとき、javactl を使うとプログラムを簡単に起動することができます。
おもな特長は、次のとおりです。

  • pip コマンドで簡単インストールすることができる
  • 起動するための設定ファイルは、YAML 形式で記述することができる

まず、インストールは、pip コマンド一発です。


$ pip install javactl

インストールすると、javactl というプログラムがインストールされます。


$ which javactl
/usr/bin/javactl
$ /usr/bin/javactl -h
Usage: javactl [options...] [args...]

Options:
--version show program's version number and exit
-h, --help show this help message and exit
--check dry-run mode
--debug debug mode

使い方は、とても簡単で設定ファイル名パスを渡すだけです。


$ javactl foo.yaml

設定ファイルの例は、こちらをみると、一目瞭然だと思います。

設定ファイルには、おもに次のような内容を記述します。

  • 対象の Java アプリケーション名 app)
  • アプリケーションの実行ユーザ名
  • 環境変数を定義したい場合は、その設定 (os.env)
  • java のヒープサイズなどのメモリ設定 (java.memory)
  • JMX がある場合、その設定 (java.jmx)
  • java の定義名 (java.env)
  • その他の Java オプション (java.option)
  • ログ(GC)ファイルの設定 (log)

Java に特化した起動パラメータをほぼすべて指定できるので Java/Scala アプリケーションをとりあえずさくっと起動したいには手軽なツールです。

僕の場合は、この javactl を使って Supervisor から Scala アプリケーションを実行するようにしています。

このエントリーをはてなブックマークに追加

Packer を使って EC2 AMI をビルドする

AWS EC2 AutoScaling するときなどには、AMI を作成しておくと、早く本番環境に投入できてとても便利です。

AMI を作成するには、packer を使うと、とても簡単にビルドすることができます。

今回は、公式の CentOS 7 の AMI をベースに、次の AMI を順番に作成してみることにしました。
Packer のサンプルテンプレートを GitHub においておきました。

  1. CentOS 7 AMI 最新版
  2. サービス共通の AMI(上の AMI をベースにして作成する)
  3. サービスのロール別の AMI(上の AMI をベースにして作成する)

CentOS 7 公式の AMI イメージ ID は、次のaws-cli コマンドで取得することが出来ます。(若干時間がかかります)

$ aws --output text ec2 describe-images \
    --owners aws-marketplace \
    --filters \
      Name=architecture,Values=x86_64\
      Name=virtualization-type,Values=hvm \
      Name=hypervisor,Values=xen \
      Name=owner-alias,Values=aws-marketplace \
      Name=root-device-name,Values=/dev/sda1 \
      Name=root-device-type,Values=ebs \
      Name=image-type,Values=machine \
      Name=state,Values=available \
      Name=description,Values="CentOS Linux 7 x86_64 HVM EBS 20150928_01" \
    --query 'Images[].ImageId'

テンプレートの変数は、すべて環境変数で渡すようになっています。

まず、CentOS 7 AMI 最新版 では、次のようなことをやっています。

  1. ルートディスク 30G、EBS 100GB にする
  2. タグを Name、ベースになった Base_AMI で設定する
  3. SELinux を無効にする(石川さんごめんなさい
  4. EPEL と必要な Ansible などをインストールする
  5. CentOS を最新にアップグレードする
  6. タイムゾーンを東京に変更する

残りのサービスごと、サービスロールごとのテンプレートは、ec2-userdata.sh というシェルスクリプトでそれぞれ必要な処理(Ansible によるプロビジョニングなど)を実行するようにします。

さらに今回は CircleCI を使っているので、CircleCI 上で特定のブランチのときのみ AMI を順番にビルドするように設定しました。
cirlce.yml に、例えば次のように記述をするとできます。

...
deployment:
  packer:
    branch: develop-packer
    commands:
      - ci-deploy-packer.sh

この例では、develop-packer ブランチに push されたとき packer を使って AMI を作成する ci-deploy-packer.sh スクリプトを実行するという意味になります。

実際のプロジェクトでは、CentOS 7 共通 1 つ、サービス共通 1 つ、ロールごと 2 つ、を直列で作成して、約 1 時間ほどかかりました。

このエントリーをはてなブックマークに追加

GitHub 上などにある Gem をインストールする方法

GitHub 上にあるとあるフォークした Gem(Rubygems にはリリースされていない)をインストールすることになりました。

普通なら、Gemfile を書いて bundle 経由でインストールするのが一般的だと思いますが、1つの Gem だったので、何か他の方法がないかなと調査したところ、specific_install というコマンドを使うとできました!

使い方は、とても簡単です。

$ sudo gem install --no-document specific_install
$ sudo specific_install -l https://github.com/<リポジトリ>.git -b <ブランチ名>

という感じでインストールすることができます!
今回、td-agent の flunetd plugin で特別に1つカスタマイズされたものがあって、無事 GitHub 上にあるとあるフォークした fluentd plugin をインストールすることができました。
td-agent の場合は、gem コマンドの代わりに td-agent-gem を使えば良いので、とても便利でした。

このエントリーをはてなブックマークに追加

Ansible 2.0 にアップグレードした

Ansible 2.0 がようやくリリースされましたね。さっそく、既存の Playbook をアップグレードしてみました。公式の移行ガイドは、こちらにあります。

まず、実行してみると、次のような警告が表示されました。

[DEPRECATION WARNING]: Instead of sudo/sudo_user, use become/become_user and make sure become_method is 'sudo' (default).
This feature will be removed in a future release. Deprecation warnings can be disabled by setting
deprecation_warnings=False in ansible.cfg.

今まで、実行ユーザを指定するとき sudo と sudo_user を使っていましたが、1.9 の頃にはすでに become / become_user になっていたので、sudo / sudo_user から、例えば centos というユーザで実行したいとき、次のように置き換える必要があります。

become: yes
become_user: centos

Ansible 2.0 では、数多くのモジュールが追加されていますが、この中で AWS ELB に SSL 証明書を追加するためのモジュール iam_cert を手動で組み込んでいましたが、これも自分のリポジトリからは削除することができてさっぱりしました。

AWS S3 モジュールをけっこう使っていますが、この中で mode=list というバケットの一覧表示するモードが追加されたのは便利です。ローカルファイルをまとめて同期するための mode=sync もほしいところです。余裕があったらフィードバックしてみたいと思います。

1.9 では、lookup(‘file’) に不具合があって実質使えない状態になっていましたが、2.0 になって無事直って使えるようになりました。

公式移行ガイドにあったエスケープ問題に遭遇しました。僕の場合は、Supervisor の設定ファイルを生成しているところで問題がありました。
具体的には、次のように Supervisor の設定ファイルを生成していました。

# supervisor.yml
- name: Configure program
  ini_file: dest={{ supervisor.config.dir }}/{{ program_name }}.ini
            section=program:{{ program_name }}
            option={{ item.option }}
            value={{ item.value }}
            owner=root
            group=supervisor
            mode=0644
  notify:
    - reload supervisord
  with_items:
    - { option: "command", value: "\"{{ program_command }}\"" }

使う側は、次のような感じです。

- name: install supervisor configuration - foo
  include: supervisor.yml
  vars:
    program_command: "foo bar"

1.9 のときは、この状態だと、program_command = foo bar という内容で出力されていましたが、2.0 になると program_command = “foo bar” という内容で出力されて Supervisor が動作しませんでした。
次のように書き換えました。

# supervisor.yml
- name: Configure program
  ini_file: dest={{ supervisor.config.dir }}/{{ program_name }}.ini
            section=program:{{ program_name }}
            option={{ item.option }}
            value={{ item.value }}
            owner=root
            group=supervisor
            mode=0644
  notify:
    - reload supervisord
  with_items:
    - { option: "command", value: "{{ program_command }}" }

使う側も次のように書き換えました。

- name: install supervisor configuration - foo
  include: supervisor.yml
  vars:
    program_command: foo "bar"

これで、program_command = foo “bar” と出力されて、Supervisor が無事動かすことができました。エスケープまわりは、2.0 になると要注意なところの1つです。

あと、ある playbook から相対パスで他の playbook を include していて template を include 側で指定している場合、include 元からの相対パスを指定しないといけなくなりました。

例えば、次のような Datadog agent の設定ファイルを生成している task conf.yml があります。

# roles/datadog/tasks/conf.yml
- name: copy agent configuration - {{ name }}
  template: src={{ template }}.yaml.j2 dest=/etc/dd-agent/conf.d/{{ name }}.yaml mode=0640 owner=dd-agent group=root
  notify:
    - restart datadog-agent
  tags: datadog

これを別の playbook から、次のように読み込んでいます。

# roles/foo/tasks/main.yml
- name: install datadog-agent - postfix
  include: ../datadog/tasks/conf.yml
  vars:
    name: postfix
    template: postfix

1.9 のときは、このときは、roles/datadog/templates/postfix.yaml.j2 が conf.yml で参照されますが、2.0 では正しく参照されませんでした。次のように書き換える必要があります。(この変更が一番個人的には大きかったです)

# roles/foo/tasks/main.yml
- name: install datadog-agent - postfix
  include: ../datadog/tasks/conf.yml
  vars:
    name: postfix
    template: ../datadog/templates/postfix

あと、うれしい点としては、今まで name に次のように変数で書いた場合、出力内容に変数名のまま出力されていましたが、2.0 になって変数は正しく展開されて表示されるようになりました。

- name: set javactl_heap by {{ ansible_ec2_instance_type }} - {{ app_name }}

最後に human_log.py という Ansible の出力結果を見やすくするプラグインを愛用していますが、2.0 になってプラグイン API の仕様が変更されて動作しなくなりました。human_log.py は、実際にはいくつか不具合が直っているこちらのものを使っています。

human_log.py を使うと、次のように出力が見やすくなるので、おすすめのプラグインです。

TASK: [command echo "TEST"] ***************************************************
changed: [127.0.0.1] => {"changed": true, "cmd": ["echo", "TEST"], "delta": "0:00:00.067706", "end": "2016-01-19 17:50:37.486635", "rc": 0, "start": "2016-01-19 17:50:37.418929", "stderr": "", "stdout": "TEST", "warnings": []}

cmd: echo TEST

start: 2016-01-25 07:50:37.418929

end: 2016-01-25 07:50:37.486635

delta: 0:00:00.067706

stdout: TEST

Ansible 2.0 プラグインの変更点はドキュメントで見つからなかったので、ソースコードを参照してみました。
human_log.py を Ansible 2.0 に対応したバージョンは、こちらにおいておきました。

human_log.py は、次のようにインストールします。CentOS 7 で Ansible 1.9 の RPM をインストールした上で、pip install -U ansible している環境上が前提になります。

$ wget -N \
    https://raw.githubusercontent.com/n0ts/ansible-human_log/master/human_log.py \
    -P /usr/share/ansible_plugins/callback_plugins/
$ sudo sed -i 's/#library        = \/usr\/share\/my_modules\//library =        \/usr\/share\/ansible\/modules/g' /etc/ansible/ansible.cfg

一点問題があって、yum で http 経由の場合、なぜか “found available, installed or updated” エラーが発生するので ignore_errors を追加しています。
次のような感じのとき、パッケージは正しくエラーになります。

- name: install packages
  yum: name="https://packages.exameple.com/{{ item }}.el7.x86_64.rpm" state=present
  with_items:
    - foo-1.1.1-1
    - bar-1.2.1-1
  ignore_errors: yes

こんな感じで無事 Ansible 1.9 から、2.0 へアップグレードすることができました。

参考

このエントリーをはてなブックマークに追加

Rocketeer を投入したお話

PHP という技術制約がある案件で、Rocketeer という一言で言ってしまうと Capistrano の PHP 版になります。
Slack とも連携できていい感じです。

Rocketeer のおもな特長は、次のとおりです。

  • GIT/SVN リポジトリに対応
  • SSH の場合、SSH キーパスフレーズにも対応
  • 任意のデプロイタイミングで、特定の処理を実行することができる

今回は、次のような感じでデプロイするのに使いました。

  • 複数のサーバに SSH 経由でログインする
  • Private Git リポジトリを ssh 経由(SSH キーパスフレーズ付き) clone する
  • デプロイ前後で Slack の専用チャンネルに通知する

実際に使ったところ、いくつか問題があったので、本家にフィードバックはしましたが、今日現在になってもマージされていません。

1. 複数のサーバがデプロイ対象のとき、正しいリビジョン番号でロールバックできない(PR #591

2. Rocketeer の実行ディレクトリがデプロイ対象の git リポジトリでないとき、正しく git のリビジョンを取得できない(PR #592

その他、不具合のではないですが、次の PR もしました。

1. デプロイ対象のユーザが分かるように Notify プラグインに –user の追加(PR #593

2. 各デプロイタスクの前に before / after フックイベントを追加(PR #594

この他、実際に運用を初めて見て1つ次のような問題が発覚しました。

1. 同じサーバ上で、同時に複数のプロジェクトをデプロイする場合、$HOME/.deploy.json ファイルを作って Rocketeer はデプロイ対象サーバを管理しているのですが、なんだか混ざってしまい正しい対象サーバへデプロイすることができない

この問題はさすがに致命的でした。おそらく、そもそも同じサーバ上で同時に複数のプロジェクトをデプロイすることができないことは考えていなかったのではないかと思われます。

この件は、PR をしようかなと思ったのですが、先の PR がすっかり無視されてしまう状態だったので、自分のもの方で修正しました。修正内容としては、デプロイスクリプト上の実行ディレクトリに .deploy.json ファイルを置くように develop ブランチで簡易改良しました。

ということで、この改善を行った結果、デプロイ自体は問題なくすることができました。

頑張って PR したのですが、どうも積極的には開発にはもうあまり取り組んでいない様子なので、Rocketeers の選択はもしかしたら失敗したかもしれませんが、どうしても PHP だけで簡潔したいという場合には、実際には問題なくデプロイできているので大丈夫だと思います。

Rocketeers の設定ファイルは、けっこうめんどくさいので、テンプレートを作ってみました。
このテンプレートを使えば簡単に Rocketeers を使うことができると思います。
テンプレートを clone したあと、僕オリジナルのカスタマイズが入った Rocketeers をインストールします。


$ composer install

設定ファイルは、.rocketeer ディレクトリの中にあります。簡単ですが設定ファイルの説明は、次のとおりです。

  • config.php: デプロイ先のサーバを設定します
  • connections: デプロイ環境ごとの設定ファイルです、テンプレートではローカルホスト環境用の設定ファイルが入っています
  • hooks.php: デプロイの前後のフックの設定ファイルです、デプロイ前後に何らかの処理を追加した場合にはここに設定します
  • paths.php: デプロイ時に利用する各種パスです、基本的には変更する必要はありません
  • plugins: プラグインの設定ファイルです、テンプレートでは Slack プラグインの設定ファイルが入っています
  • remote.php: デプロイ先のディレクトリの設定ファイルです、ファイルのパーミッションなどを変更するときに設定します
  • scm.php: デプロイリポジトリの設定ファイルです、リポジトリの設定をします
  • stages.php: デプロイステージの設定ファイルです
  • strategies: この設定ファイルはよく分かっていません
  • starateies.php: デプロイタスクごとに何を使用するのか設定するファイルです、デプロイ時の動作を変更したいときに設定します

デプロイするコマンドは、次のとおりです。

$ ./vendor/bin/rocketeer deploy --on=local --user=< デプロイユーザ名> -vv

–on はデプロイ環境、–user はデプロイユーザ名(Slack にもこのユーザ名として通知されます)、-vv はデバッグオプション、になります。
–pretend を指定するといわゆる DRY RUN モードになります。

ロールバックするには、次のようになります。

$ ./vendor/bin/rocketeer rollback --on=production --user=< デプロイユーザ名>

このように Capistrano と同じ感じで利用できると思います。

デプロイしたときのログは、.rocketeer/logs/ ディレクトリ以下に出力されます。

Rocketeer ですが、現在はあまりメンテナンスされていない様子ですが、さくっと Capistrano のようなデプロイスクリプトを作りたいときには、便利なツールの1つだと思います。