こんにちは。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 を実践するノウハウが満載!]
- 作者: 吉羽龍太郎,新原雅司,前田章,馬場俊彰
- 出版社/メーカー: 技術評論社
- 発売日: 2016/02/26
- メディア: Kindle版
- この商品を含むブログを見る
前提条件
今回は以下の設定は予め行われていることとします。
- CircleCIへのサインアップ
- GitHubへのサインアップ
- これまで作成したソースコードをGitHubリポジトリで管理
- CircleCIとGitHubの連携
※GitHubの利用方法がわからない時にはこちらの書籍が参考になります。私もこの書籍でGitHubについて理解が進みました。 ※CircleCIの利用方法がわからない時にははじめての CircleCIが参考になります。
CircleCIとDigitalOceanの設定について
CircleCIでこれまで作成したソースコードのビルドを実行すると失敗します。それは以下の設定がCircleCIに行われていないためです。
- DigitalOceanのDrop(仮想マシン)へ接続するためのDigitalOceanの公開鍵IDが登録されていない。
- DigitalOceanを利用するためのAPIキーが環境変数に登録されていない。
- CircleCIとローカル環境では設定が異なるため
spec_helper
を変更する必要がある。 - CircleCIではGemのインストールにはGemfileを利用する必要がある。
また、同様にDigitalOceanでも以下の設定が行われていないためビルドがエラーとなります。
- DigitalOceanにCircleCIに対応した公開鍵が登録されていない。
上記の設定をこれから行います。
CircleCIからDigitalOceanのDropへ接続するための公開鍵IDを登録する
CircleCIからDigitalOceanのDropへ接続するためには以下の設定が必要となります。
- CircleCIに秘密鍵を登録する。
- CircleCIに登録した秘密鍵に対応する公開鍵をDigitalOceanに登録する。
- DigitalOceanのAPIから登録した公開鍵に対応するssh_keys_idを取得する。
- 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-----
設定するための画面へは以下のとおり遷移します。
INSIGHTSをクリックします。
INSIGHTS画面で対象のプロジェクトの設定ボタンをクリックします。
ProjectSettings の SSH Permissionをクリックします。
これにより下記の通りSSHの秘密鍵を登録する画面が表示されるので先ほどの秘密鍵を登録します。
CircleCIに登録した秘密鍵に対応する公開鍵をDigitalOceanに登録する
DigitalOceanに公開鍵を登録する手順は以下のとおりとなります。
- DigitalOceanにログインする。
Setting画面を表示する。
Security画面を表示する。
SSH KeysでAdd SSH Keysボタンをクリックする。
公開鍵を登録する。
ここで登録する公開鍵は、先ほど作成した鍵のうち公開鍵となります。
╭─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の環境変数へ登録します。登録する方法は以下のとおりとなります。
INSIGHTSをクリックします。
INSIGHTS画面で対象のプロジェクトの設定ボタンをクリックします。
Build Settings
にあるEnvironment variables
をクリックします。Name
にTF_VAR_ssh_keys_id
を入力し、Valueに先ほど取得したssh_keys_id
を入力します。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秒ほどでしょうか。)
ということで、circle.yml
を少し修正しました。sleep 30;
をTerraform apply
のあとに追加しました。
machine: ... test: pre: ... - sleep 30; override: ...
これでCircleCIでのビルドはSuccessとなりました。
まとめ
いろいろと設定が手間に感じるかもしれませんが、ここまで自動化できることはとても素晴らしいことだと思います。
CircleCIへビルドを実行するとDigitalOcianでDropletが起動して、Chefが環境を構築する。そのあと、ServerSpecが環境のテストをして問題がなければDropletは破棄される。
この感じが歯車が咬み合って大きな力が働くような、そんな感じがしてとても楽しいです。