サーバ/インフラエンジニア養成読本 DevOps編(特集2 第3章後半)

こんにちは。beaglesoftの真鍋です。

前回のblog.beaglesoft.net に続き、サーバ/インフラエンジニア養成読本 DevOps編 [Infrastructure as Code を実践するノウハウが満載! ] (Software Design plus)の特集2 第3章の後半部分を進めたいと思います。

第3章の後半

第3章の後半は前半でローカル環境で作成したTerraform+DigitalOceanへの設定をCircleCIからビルド時に実行できるようにします。

内容はサーバ/インフラエンジニア養成読本 DevOps編 [Infrastructure as Code を実践するノウハウが満載! ] (Software Design plus)に書いてある内容を実際に試していますので、書籍を参考にしてください。

サーバ/インフラエンジニア養成読本 DevOps編 [Infrastructure as Code を実践するノウハウが満載!]

サーバ/インフラエンジニア養成読本 DevOps編 [Infrastructure as Code を実践するノウハウが満載!]

前提条件

今回は以下の設定は予め行われていることとします。

  1. CircleCIへのサインアップ
  2. GitHubへのサインアップ
  3. これまで作成したソースコードをGitHubリポジトリで管理
  4. CircleCIとGitHubの連携

※GitHubの利用方法がわからない時にはこちらの書籍が参考になります。私もこの書籍でGitHubについて理解が進みました。 ※CircleCIの利用方法がわからない時にははじめての CircleCIが参考になります。

CircleCIとDigitalOceanの設定について

CircleCIでこれまで作成したソースコードのビルドを実行すると失敗します。それは以下の設定がCircleCIに行われていないためです。

  1. DigitalOceanのDrop(仮想マシン)へ接続するためのDigitalOceanの公開鍵IDが登録されていない。
  2. DigitalOceanを利用するためのAPIキーが環境変数に登録されていない。
  3. CircleCIとローカル環境では設定が異なるためspec_helperを変更する必要がある。
  4. CircleCIではGemのインストールにはGemfileを利用する必要がある。

また、同様にDigitalOceanでも以下の設定が行われていないためビルドがエラーとなります。

  1. DigitalOceanにCircleCIに対応した公開鍵が登録されていない。

上記の設定をこれから行います。

CircleCIからDigitalOceanのDropへ接続するための公開鍵IDを登録する

CircleCIからDigitalOceanのDropへ接続するためには以下の設定が必要となります。

  1. CircleCIに秘密鍵を登録する。
  2. CircleCIに登録した秘密鍵に対応する公開鍵をDigitalOceanに登録する。
  3. DigitalOceanのAPIから登録した公開鍵に対応するssh_keys_idを取得する。
  4. CircleCIの環境変数にssh_keys_idを登録する。

CircleCIに秘密鍵を登録する

CircleCIに秘密鍵を登録するのですが、以前利用した秘密鍵ではなく新規に作成した秘密鍵をCircleCIに登録します。

╭─ymanabe@Yoichiro-no-MacBook-Pro  ~ ‹2.2.4›
╰─$ ssh-keygen -t rsa -b 4096 -C "circleci-ssh-key" -f circleci
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in circleci.
Your public key has been saved in circleci.pub.

╭─ymanabe@Yoichiro-no-MacBook-Pro  ~ ‹2.2.4›
╰─$ ls -al | grep circle                                                                                                                                                      1 ↵
-rw-------    1 ymanabe  staff    3243  4  4 21:10 circleci
-rw-r--r--    1 ymanabe  staff     742  4  4 21:10 circleci.pub

ここで作成されたcircleciが秘密鍵でcircleci.pubが公開鍵となります。CircleCIには秘密鍵を登録しますが、以下のとおり表示した内容を貼り付けます。

$ cat circleci
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----

設定するための画面へは以下のとおり遷移します。

  1. INSIGHTSをクリックします。 Insights - CircleCI 2016-04-05 07-41-33.png (10.7 kB)

  2. INSIGHTS画面で対象のプロジェクトの設定ボタンをクリックします。 Insights - CircleCI 2016-04-05 07-42-02.png (21.7 kB)

  3. ProjectSettings の SSH Permissionをクリックします。 Project settings - yoichiro-manabe:server-infra-devops-chap2 - CircleCI 2016-04-05 07-42-27.png (60.5 kB)

これにより下記の通りSSHの秘密鍵を登録する画面が表示されるので先ほどの秘密鍵を登録します。

Monosnap 2016-04-05 07-36-41.png (259.1 kB)

CircleCIに登録した秘密鍵に対応する公開鍵をDigitalOceanに登録する

DigitalOceanに公開鍵を登録する手順は以下のとおりとなります。

  1. DigitalOceanにログインする。
  2. Setting画面を表示する。 DigitalOcean - Droplets 2016-04-05 10-16-34.png (36.0 kB)

  3. Security画面を表示する。 DigitalOcean - Control Panel 2016-04-05 10-17-11.png (16.1 kB)

  4. SSH KeysでAdd SSH Keysボタンをクリックする。 DigitalOcean - Control Panel 2016-04-05 10-17-44.png (81.9 kB)

  5. 公開鍵を登録する。 DigitalOcean - Control Panel 2016-04-05 10-19-01.png (32.5 kB)

ここで登録する公開鍵は、先ほど作成した鍵のうち公開鍵となります。

╭─ymanabe@Yoichiro-no-MacBook-Pro  ~ ‹2.2.4›
╰─$ cat circleci.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCyCHeM9oidYRq+kY6Kwx0CTISfpRrrca32SNxs3RVD62RGzuJFC+JTsGoU0FBySNbdW8Ohxw9xhcB99DALHIIyzjp6fbVMQoI9xGhnGiVgEjBvtR+jZONdJEm8OlqdXEG2/LkZxGrruWtIa+FFVKEymQ5K5btAJkFPG7z4EhvzKxc5FhWWHW2JLxw69BhG2ptzUA8+8zYg/WV/qLUmRGglI6UbsG3PNwYw4GJ7Bqkb9t8MffWb+VX0ziGnZtnjr+5xIKoiNtkHtjSJz2aD0Tjq7nl1PLEMrwgwtNiVcISbfi7lG4NFG85hdx7tSv2EuNSba6Wg7BSNeC1n/pjdb9EH5vv+LwX10X5lJkbqSbtGvWzNymtHMynMYCG7P9t7C6wtaOUPJQt6Mo68CKRKdOyWhbemep6rzdNMJob5eDLbsuNwvWmkpz93SxvIIB+AzzfyEYdpIqu8RErx2j842oGnbHeuQeDuhFXzd6+jxDK2ITOLJ0HRVZaZ7ig+d+Ps3kt/C79pxGV+XNaG2uJQ8RLLpDaKWqcyJcF9icqnZjDNpEqsEibjwVdTJm/e2nsEs5H5dr8LUBek9IMtS9souyIWkAHnmvxy9fOHCeymrzuzchwNZnGojQMoIKoyc1KKfl6kdPqxWRuOj62OP2O5viaQQVXE/VZ6ZjUDYa93iv/8Aw== circleci-ssh-key

DigitalOceanのAPIから登録した公開鍵に対応するssh_keys_idを取得する

先ほど登録した公開鍵に対応するssh_key_idをDigitalOceanから取得します。

# あらかじめ DIGITALOCEAN_TOKEN にAPIKeyを登録します。
╭─ymanabe@Yoichiro-no-MacBook-Pro  ~ ‹2.2.4›
╰─$ export DIGITALOCEAN_TOKEN=API_KEY

╭─ymanabe@Yoichiro-no-MacBook-Pro  ~ ‹2.2.4›
╰─$ curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer ${DIGITALOCEAN_TOKEN}" "https://api.digitalocean.com/v2/account/keys" | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   541    0   541    0     0    473      0 --:--:--  0:00:01 --:--:--   474
{
  "ssh_keys": [
    {
      "id": xxxxxx,
      "fingerprint": "finger print...",
      "public_key": "public key...",
      "name": "mac-book-pro"
    }
  ],
  "links": {},
  "meta": {
    "total": 1
  }
}

ここで取得したssh_key_idはCircleCIへ登録します。

CircleCIの環境変数にssh_keys_idを登録する

先ほど取得したssh_key_idをCircleCIの環境変数へ登録します。登録する方法は以下のとおりとなります。

  1. INSIGHTSをクリックします。 Insights - CircleCI 2016-04-05 07-41-33.png (10.7 kB)

  2. INSIGHTS画面で対象のプロジェクトの設定ボタンをクリックします。 Insights - CircleCI 2016-04-05 07-42-02.png (21.7 kB)

  3. Build SettingsにあるEnvironment variablesをクリックします。 Project settings - yoichiro-manabe:server-infra-devops-chap2 - CircleCI 2016-04-05 10-52-15.png (30.0 kB)

  4. NameTF_VAR_ssh_keys_idを入力し、Valueに先ほど取得したssh_keys_idを入力します。 Project settings - yoichiro-manabe:server-infra-devops-chap2 - CircleCI 2016-04-05 12-08-50.png (80.0 kB)

  5. Save variablesボタンをクリックして保存します。

spec_helperの修正

ローカル環境とCircleCIでは実行する環境が異なるためspec_helperを編集します。

require 'serverspec'
require 'net/ssh'
require 'tempfile'

set :backend, :ssh

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 = false }
else
  set :sudo_password, ENV['SUDO_PASSWORD']
end

host = ENV['TARGET_HOST']

# 以下の内容を変更している。環境変数が`CIRCLECI`のときはssh設定を利用する。
if ENV['CIRCLECI']
  options = Net::SSH::Config.for(host, ["~/.ssh/config"])
  options[:user] = "root"
else
  `vagrant up #{host}`

  config = Tempfile.new('', Dir.tmpdir)
  config.write(`vagrant ssh-config #{host}`)
  config.close

  options = Net::SSH::Config.for(host, [config.path])
  options[:user] ||= Etc.getlogin
end

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

# Disable sudo
# set :disable_sudo, true


# Set environment variables
# set :env, :LANG => 'C', :LC_MESSAGES => 'C' 

# Set PATH
# set :path, '/sbin:/usr/local/sbin:$PATH'

Gemfileに依存関係を設定する

ローカルではgem installにより必要なGemを直接インストールしていましたが、CircleCIではGemfileへ依存関係を記述してbundlerを利用したインストールを行う必要があります。プロジェクトのルートディレクトリでGemfileを作成します。

$ touch Gemfile

次に、作成したGemfileに依存するgemを記述します。

source 'https://rubygems.org'

gem 'knife-solo'
gem 'serverspec'

circle.ymlを作成する

最後は肝心のCircleCIのビルド設定を行います。この設定はcircle.ymlに記述します。まずはプロジェクトのルートディレクトリにcircle.ymlファイルを作成します。

$ touch circle.yml

次に作成したcircle.ymlファイルに以下の内容を記述します。

machine:
  ruby:
    version: 2.2.4
  environment:
    TERRAFORM_VERSION: 0.6.14
dependencies:
  cache_directories:
    - ~/.terraform
  pre:
    - |
      mkdir -p $HOME/.terraform

      if [ -z "$(ls -A $HOME/.terraform/terraform_${TERRAFORM_VERSION}_linux_amd64.zip)" ]; then
        cd $HOME/.terraform
        curl -LO https://releases.hashicorp.com/terraform/0.6.14/terraform_${TERRAFORM_VERSION}_linux_amd64.zip
        unzip -o terraform_${TERRAFORM_VERSION}_linux_amd64.zip
      fi

    - sudo cp -fR $HOME/.terraform/* /usr/local/bin

test:
  pre:
    - terraform plan
    - terraform apply
    - cp /etc/hosts .
    - sudo bash -c "cat hosts /tmp/hosts.txt | tee /etc/hosts"
  override:
    - bundle exec knife solo prepare root@webapp
    - bundle exec knife solo cook root@webapp
    - bundle exec rake spec
  post:
    - terraform plan -destroy
    - terraform destroy -force

この設定で実行するとビルドエラーとなりました。

$ bundle exec knife solo prepare root@webapp
Bootstrapping Chef...
ERROR: Network Error: Connection refused - connect(2) for "webapp" port 22
Check your knife configuration and network settings

bundle exec knife solo prepare root@webapp returned exit code 100

$ bundle exec knife solo cook root@webapp
Running Chef on webapp...
Checking Chef version...
ERROR: Network Error: Connection refused - connect(2) for "webapp" port 22
Check your knife configuration and network settings

bundle exec knife solo cook root@webapp returned exit code 100

$ bundle exec rake spec
/opt/circleci/ruby/ruby-2.2.4/bin/ruby -I/home/ubuntu/server-infra-devops-chap2/vendor/bundle/ruby/2.2.0/gems/rspec-core-3.4.4/lib:/home/ubuntu/server-infra-devops-chap2/vendor/bundle/ruby/2.2.0/gems/rspec-support-3.4.1/lib /home/ubuntu/server-infra-devops-chap2/vendor/bundle/ruby/2.2.0/gems/rspec-core-3.4.4/exe/rspec --pattern spec/webapp/\*_spec.rb
/home/ubuntu/server-infra-devops-chap2/vendor/bundle/ruby/2.2.0/gems/net-ssh-2.9.4/lib/net/ssh/transport/session.rb:70:in `initialize': Connection refused - connect(2) for "webapp" port 22 (Errno::ECONNREFUSED)
....
    from /home/ubuntu/server-infra-devops-chap2/vendor/bundle/ruby/2.2.0/gems/rspec-core-3.4.4/exe/rspec:4:in `<main>'
/opt/circleci/ruby/ruby-2.2.4/bin/ruby -I/home/ubuntu/server-infra-devops-chap2/vendor/bundle/ruby/2.2.0/gems/rspec-core-3.4.4/lib:/home/ubuntu/server-infra-devops-chap2/vendor/bundle/ruby/2.2.0/gems/rspec-support-3.4.1/lib /home/ubuntu/server-infra-devops-chap2/vendor/bundle/ruby/2.2.0/gems/rspec-core-3.4.4/exe/rspec --pattern spec/webapp/\*_spec.rb failed

bundle exec rake spec returned exit code 1

ERROR: Network Error: Connection refused - connect(2) for "webapp" port 22

とあるのでDigitalOceanのDropletsへSSHで接続ができていないようです。鍵の情報などを設定しなおしたのですが、どうにもうまく接続できませんでした。

トラブルシュート

試しにローカル環境で接続ができるかを確認しましたが、やはり同様にDropletは作成されるのですがエラーとなります。ただ、少し間を置いてから接続すると接続できます。

20秒ではだめで30秒ほど待つ必要があります。DigitalOceanのコンソール画面を確認するとSSH鍵の設定で37秒程度かかっているようです。(ローカルから実施した下記の場合には40秒ほどでしょうか。)

DigitalOcean Droplet Console 2016-04-05 12-38-29.png (244.0 kB)

ということで、circle.ymlを少し修正しました。sleep 30;Terraform applyのあとに追加しました。

machine:
...
test:
  pre:
...
    - sleep 30;
  override:
...

これでCircleCIでのビルドはSuccessとなりました。

まとめ

いろいろと設定が手間に感じるかもしれませんが、ここまで自動化できることはとても素晴らしいことだと思います。

CircleCIへビルドを実行するとDigitalOcianでDropletが起動して、Chefが環境を構築する。そのあと、ServerSpecが環境のテストをして問題がなければDropletは破棄される。

この感じが歯車が咬み合って大きな力が働くような、そんな感じがしてとても楽しいです。