blogの再構築

Vagrant + Ansible で既存環境構築のコード化を行った。CDはしてない。 以前から放置していたのが気になっていて、気楽に改善できるようにするために実施した。

目的が環境の可搬性向上なので作成後にはPacker対応を試してみた。 しかし利用しているAnsibleのロールが動かなかったため諦めて、 ここまでについて記録することにした。

利用したミドルウェアやツールに関して調べたことは個別に書く。 Vagrant, Ansible, Makefileで繋げた部分をメモする。

達成目標

外部に公開する環境をPublic環境(会社だとProductionとか呼ぶやつ)、システムテストとか目視確認を行う環境をQA環境と呼ぶことにした。主に以下を目指した。

  • 依存管理とデプロイ準備は1コマンドで完了させる(Makefile)
  • QA環境,Public環境とも同じAnsible playbookを利用する
  • 環境の違いはAnsible inventoryファイルだけで判断される
  • QA環境の仮想マシンはVritualBox+Vagrantで作る
  • QA環境のinventoryファイルはVagrantが自動生成するものを利用する
  • Ansible のディレクトリレイアウトはベストプラクティスに従う
  • Ansible playbookはできるだけroleを活用する
  • Ansible roleは設定ファイルなどのテンプレートを差し替えられるものを選ぶ
  • TLSのサーバー証明書は不用意に更新しない
  • リポジトリ内に鍵は含めない

作ったリポジトリ

2つのリポジトリを作った。

まずQA環境用にvagrant-qaを準備しここにVagrantファイルを置いている。 プロビジョニングはAnsibleを採用しサーバーplaybookはansible-blog-serverとして分離した。

このvagrant-qaはMakefileでansible-blog-serverを取得する(sub moduleにしていない)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ (cd .. && tree -L 4)
.
`-- vagrant-qa
    |-- Makefile
    |-- Vagrantfile
    `-- provisioning
        `-- ansible_masu-mi.me-server
            |-- Makefile
            |-- README.md
            |-- files
            |-- requirements.yml
            |-- roles
            |-- site.yml
            `-- templates

6 directories, 7 files

構築対象

作成対象のblogサーバーは下の通りでgitを使った簡素な構成にした。

  • HTTPサーバにNginxを利用する
  • ドキュメントの管理はgitoliteを使う
  • 秘密鍵・証明書の管理はcertbot-auto
  • firewallはiptablesのスクリプトの実行で済ませる
  • SELinuxは無効にする(難しいから, そのうち有効にする)

図にすると下な感じ。

1
2
3
4
5
-[block!] by iptables
-(http(s))-> WebServer(Nginx)
               |--(read)-> [file] <-(write)-- gitolite
               |--(use)-> privkey.pem,fullchain.pem <-(update)- certbot-auto
               |--(use)-> dhparam.pem

苦労したところ

  • iptablesの存在を忘れててport:443でnginxに到達できずに焦った
  • SELinuxを分かっていないくてpermissionだけみて閲覧できない事に焦った
  • certbot-autoの稼働可否を環境(QA or Public)に依存させる方法
  • TLS証明書・鍵の生成ってたまにしかやらないから覚えてない
  • プロトコルスイートの優先度や推奨/非推奨の理解
  • vagrant private_networkをdhcpで動かすとvagrant-hostsupdaterなどが機能しない

Vagrant

構築対象が既にVPS上にあり互換を崩したくなかったため、新規でコンテナに構築するのは避けた。 VagrantのプロバイダはをVirtualBoxにしている。

公式のBoxはAtlasで検索する。 欲しいdistribution, versionを見つけたら /providers/${provider}.box をパスの末尾に追加したURLをbox_urlに設定すると取得できる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# mode: ruby
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.define "centos.qa.lonet" do |node|
    node.vm.box = "centos/7"
    node.vm.box_url = "https://atlas.hashicorp.com/centos/boxes/7/versions/1704.01//providers/virtualbox.box"

    node.vm.hostname = "centos.qa.lonet"
    node.vm.network "public_network"
    node.vm.provision "ansible" do |ansible|
      ansible.playbook = "provisioning/ansible_masu-mi.me-server/site.yml"
      ansible.limit = 'all'
      ansible.force_remote_user = "root"
      ansible.raw_arguments = [
        "-l", "centos.qa.lonet"
      ]
    end
  end
  config.vm.define "ubuntu.qa.lonet" do |node|
    node.vm.box = "ubuntu16"
    node.vm.box_url = "https://atlas.hashicorp.com/ubuntu/boxes/xenial64/versions/20170624.0.0/providers/virtualbox.box"

    node.vm.hostname = "ubuntu.qa.lonet"
    node.vm.network "public_network"
    node.vm.provision "ansible" do |ansible|
      ansible.playbook = "provisioning/ansible_masu-mi.me-server/site.yml"
      ansible.limit = 'all'
      ansible.force_remote_user = "root"
      ansible.raw_arguments = [
        "-l", "ubuntu.qa.lonet",
        "-e", "ansible_python_interpreter=/usr/bin/python3"
      ]
    end
  end
end

pluginには依存しない様に書いた。 plugin管理はでサードパーティの管理ツールを使うくらいならMakefileを使うのが楽。

ansible.raw_arguments 属性でAnsible 実行時の引数を指定できる。 またubuntu16にはpython3しかないのでansible_python_interpreter で実行インタプリタを変更している。

Ansible

Best Practicesを読んでおく。 またansible-examplesというリポジトリがあるので真似る。

ベストプラクティスとは、templates, filesといったディレクトリをトップレベルに作っているところが異なる。

構築対象に直接関わるファイル・テンプレート群は汎用性がなく変更も多いためロールではなくトップレベルに置いた。 これによって外部のロールで使われるtemplateを上書きしている。

この方針は、構築対象や環境に直接関わる変数(vars)をトップレベルやインベントリファイルに記述するベストプラクティスの延長だと思っている。

playbookを書く時は出来るだけ既成のロールを再利用するように気をつけた。

  • 選択ポイントは公式か著者が有名か?
  • メンテナンスはされているか?
  • 柔軟に使えるか?
    • テンプレートを変更できるか?
    • デフォルトのテンプレートは丁寧に作られているか?

ロールの依存管理はansible-galaxy を用いる。requirements.yml に依存ロールを書いてinstallをする。 この辺の作業はMakefileにまとめた。

1
2
3
4
5
6
- src: https://github.com/geerlingguy/ansible-role-nginx
   name: geerlingguy.nginx
- src: https://github.com/silpion/ansible-gitolite
   name: silpion.gitolite
- src: https://github.com/silpion/ansible-util
   name: silpion.util
1
$ ansible-galaxy -r ./requirements.yml -p ./roles install

変数

ロール毎に設定したりタスク実行結果を保存して利用するなど設定できる場所は色々ある。 そしてホスト毎、サーバーグループ毎、ステージ毎、に設定値を変更したい場合がある。

ホストやサーバーグループ毎についてはベストプラクティスに書かれていて、 group_vars, host_vars ディレクトリ配下かインベントリファイルに書くのが良い。

ステージ毎の変数は、ベストプラクティスに従うとステージ毎のディレクトリを掘って配下のホスト毎・サーバーグループ毎の設定で対応するかインベントリファイルに書くことになる。 これを簡単にするために環境毎に全てのグループが所属するグループを作る という方法がある。

しかしVagrant では呼ぶ出すインベントリファイルを変更したりデフォルトのインベントリファイルに手を加えることは(出来るけど)難しい。

そこでstage 変数の定義/未定義を利用してタスクを動かしたり変数を定義したりする。

1
when: stage is defined and stage == "public"

他に、ロール内ではset-factwhen: xxx is defined を組み合わせてデフォルト変数の設定などを行った。

1
2
3
set_fact:
  hoge_var: "{{ __roleA_hoge_var }}"
when: hoge_var is defined

Makefile

Makefileを久々に書いた。$@,$< とか忘れてた。

vagrant-qa/Makefile

愚直に書いた。

1
2
3
4
5
6
7
.PHONY: setup

setup: ./provisioning/ansible_masu-mi.me-server
  cd $< && gmake setup

./provisioning/ansible_masu-mi.me-server:
  git clone git@bitbucket.org:masu_mi/ansible_masu-mi.me-server.git $@

ansible_masu-mi.me-server/Makefile

愚直に書いた。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
ssh_conf_path = ~/.ssh/
files_path    = ./files/
tls_path      = $(files_path)/tls/
pub_keys_path = $(files_path)/gitolite/users/ssh/

.PHONY: setup

setup: roles $(pub_keys_path)masumi.pub $(tls_path)dhparam.pem $(tls_path)chain.pem $(tls_path)fullchain.pem $(tls_path)privkey.pem
  ansible-galaxy -r ./requirements.yml -p $< install

roles:
  mkdir $@

$(tls_path)chain.pem: $(tls_path)crt.pem
  cp $< $@

$(tls_path)fullchain.pem: $(tls_path)crt.pem
  cp $< $@

$(tls_path)crt.pem: $(tls_path)csr.pem $(tls_path)privkey.pem
  openssl x509 -days 365 -req -signkey $(tls_path)privkey.pem < $(tls_path)csr.pem > $@

$(tls_path)csr.pem: $(tls_path)privkey.pem
  openssl req -sha256 -newkey rsa:2048 -new -key $< > $@

$(tls_path)privkey.pem:
  openssl genrsa 4096 > $@

$(tls_path)dhparam.pem:
  sudo openssl dhparam 2048 -out $@

$(pub_keys_path)masumi.pub:
  cp $(ssh_conf_path)id_rsa.pub $@

だいたいの骨組みはこんな感じ。

comments powered by Disqus