直近ではDocker製ツールを用いた複数サーバへのコンテナの展開を考えていたのだが・・・
常々書いているようにホスト側のCentOS7とは相性が悪いところもあり、 またトラブル時の対応方法や他のメンバーへの周知に不安があるところ。
それに今回の案件ではマルチホストネットワークやスケーリングは必要ではないわけで、 あえて新しいツールを無理に使うよりも、 サーバのセッティングの際に使用しているAnsibleを用いた方が学習コストも抑えられるしシンプルで良いと判断した。
なにより、インフラ側と開発側が共通のツールに親しんでおくことは実運用においてメリットが大きいように思う。
Vagrantで実験環境を準備
実験環境としてお馴染みの実験環境を用意する。
以下のVagrantfileを作成し、とりあえず二台ほどCentOS7サーバを用意する。
Vagrant.configure(2) do |config|
config.vm.box = "CentOS7"
config.vm.box_url = "https://github.com/holms/vagrant-centos7-box/releases/download/7.1.1503.001/CentOS-7.1.1503-x86_64-netboot.box"
config.vm.define "app1" do |server|
server.vm.network "private_network", ip: "192.168.33.11"
end
config.vm.define "app2" do |server|
server.vm.network "private_network", ip: "192.168.33.12"
end
end
あとは立ち上げて、以降のAnsible操作で利用できるようにSSHの設定を書き込んでおく。
$ vagrant up $ vagrant ssh-config -host >> ~/.ssh/config
Ansible
さて本題。
構成管理ツールといえば以前にはChefにも挑戦したのだが、 いかんせん覚えなければいけないことが多くてしんどくて、 参考書を一通りなめたっきりになってしまった。
その点においてAnsibleはシンプルで理解しやすいのが良い。
- 作者: Lorin Hochstein,Sky株式会社玉川竜司
- 出版社/メーカー: オライリージャパン
- 発売日: 2016/04/16
- メディア: 大型本
- この商品を含むブログ (2件) を見る
導入
ひとまずは公式ドキュメントに従えば良い。
Installation — Ansible Documentation
僕の場合はUbuntu環境なので以下のコマンドでホストに導入。
$ sudo apt-get install software-properties-common $ sudo apt-add-repository ppa:ansible/ansible $ sudo apt-get update $ sudo apt-get install ansible
インベントリファイルを記述する ./hosts
作業ディレクトリにhostsという接続先を定義したファイルを作成する。
[server_a] app1 [server_b] app2 [docker_server:children] server_a server_b
app1と2とをそれぞれ別の役割を持つサーバとして定義し、 それら全てについてdockerを用いるサーバであると記載する。
個人的にはこの分類こそがAnsibleを効率的に使う上でキモの部分なように思う。
Ansibleの設定を記述 ./ansible.conf
今回はひとまず最低限に。
作業ディレクトリ直下にansible.confというファイルを作成する。
[defaults] hostfile = ./hosts remote_user = vagrant
Vagrantで立ち上げたホストにはvagrantというユーザでアクセスするの記載しておく。
dockerをインストールするroleを作成する ./roles/docker/tasks/main.yml
Ansibleでは完結した作業をroleという概念で扱う。
今回だと全てのサーバで共通してDockerを扱うため、 そのセッティングを行うroleを作成する。
作業ディレクトリ直下にrolesというディレクトリと、
その下にdockerというディレクトリを用意する。
そしてdockerディレクトリ以下に実作業を記述するtasksディレクトリを作成し、
その中に以下のmain.ymlを記述する。
ファイル配置
└── roles └── docker └── tasks └── main.yml
main.yml
---
- block:
- name: basic packages
yum: name={{item}} state=latest
with_items:
- python
- docker-python
- name: yum update
yum: name=*
- name: service check
service: name=docker state=started
register: docker_service
ignore_errors: True
- block:
- name: Get installer
get_url: url=https://get.docker.com/ dest=~/docker-install.sh validate_certs=yes
- name: Install docker-engine
shell: sh ~/docker-install.sh
- name: Reload systemd
shell: systemctl daemon-reload
- name: Start Docker
service: name=docker state=started
when: docker_service|failed
- name: Join User Group
user: name=vagrant groups=docker append=yes
必要なパッケージおよびDockerをインストールし、 操作のためにvagrantをdockerユーザグループに参加させる。
shellとかyumとかserviceといった項がAnsibleではModuleとよばれる、
実際にサーバに行わせる動作を記述する部分になる。
All Modules — Ansible Documentation
まとまった作業はblockという項目でくくることができ、
when句を用いて状況によって実行するしないを分けることもできる。
Dockerのインストールは重複しないように、事前にサービスの起動状態を判定している。
コンテナをデプロイするためのroleを作成する ./roles/deploy/tasks/main.yml
先ほどのdockerのインストールと同じ要領で、
roles以下にdeployというディレクトリを作成する。
そしてそのtasks/main.ymlとして以下の記述をする。
---
- name: Deploy Container
docker_container:
name: "{{docker_name}}"
image: "{{docker_image}}"
pull: true
restart_policy: always
state: started
ports: "{{docker_ports}}"
Docker用のモジュールはいくつもあって迷うところだが、 dockerモジュールは非推奨となっているので、 今から使うならばdocker_containerモジュールが良いだろう。
docker_container - manage docker containers — Ansible Documentation
さて、{{}}の部分は変数となっており、
今回であればapp1とapp2でそれぞれ値を変えて、
別々のコンテナを記述したい箇所である。
そのようなサーバの役割ごとの変数は作業ディレクトリ直下にgroup_varsというディレクトリを作り、
その下にサーバの役割ごとのファイルを記述すればよい。
今回は下記のものを作成した。
group_vars/server_a
docker_name: "nginx" docker_image: nginx:latest docker_ports: - 80:80 - 433:433
group_vars/server_b
docker_name: "redis" docker_image: redis:latest docker_ports: - 6379:6379
これにより、hosts内でserver_aとして定義されたapp1にはnginx、
server_bとしたapp2にはredisがデプロイされるはずである。
site.ymlの作成 ./site.yml
最後に、ansibleが直接作業内容を読むためのファイルsite.ymlを作業ディレクトリ直下に作成する。
- hosts: docker_server
become: true
roles:
- docker
- hosts: docker_server
roles:
- deploy
become項はsudoで作業させるか否かを示す。
もっと複雑になればinclude構文を使ってファイルを分割していくこともできる。
最終的な作業ディレクトリ内のファイル構成
├── Vagrantfile ├── ansible.cfg ├── group_vars │ ├── server_a │ └── server_b ├── hosts ├── roles │ ├── deploy │ │ └── tasks │ │ └── main.yml │ └── docker │ └── tasks │ └── main.yml └── site.yml
実行してみる
ansible-playbook site.ymlというコマンドで動かしてみる。
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [app2]
ok: [app1]
TASK [docker : basic packages] *************************************************
changed: [app1] => (item=[u'python', u'docker-python'])
changed: [app2] => (item=[u'python', u'docker-python'])
TASK [docker : yum update] *****************************************************
ok: [app2]
ok: [app1]
TASK [docker : service check] **************************************************
fatal: [app2]: FAILED! => {"changed": false, "failed": true, "msg": "systemd could not find the requested service \"'docker'\": "}
...ignoring
fatal: [app1]: FAILED! => {"changed": false, "failed": true, "msg": "systemd could not find the requested service \"'docker'\": "}
...ignoring
TASK [docker : Get installer] **************************************************
changed: [app1]
changed: [app2]
TASK [docker : Install docker-engine] ******************************************
changed: [app2]
changed: [app1]
TASK [docker : Reload systemd] ***************************************
changed: [app2]
changed: [app1]
TASK [docker : Start Docker] *************************************************
changed: [app1]
changed: [app2]
TASK [docker : Join User Group] ************************************************
changed: [app1]
changed: [app2]
TASK [deploy : Deploy Container] ***********************************************
changed: [app1]
changed: [app2]
1 - hosts: all
PLAY RECAP *********************************************************************
app1 : ok=10 changed=7 unreachable=0 failed=0
app2 : ok=10 changed=7 unreachable=0 failed=0
初回なので当然すべての項目をこなす。
実際にvagrant sshなどで入ってみると、 目的のコンテナがデプロイされていることが確認できる。
再度実行してみる
PLAY [all] ********************************************************************* TASK [setup] ******************************************************************* ok: [app2] ok: [app1] TASK [docker : basic packages] ************************************************* ok: [app1] => (item=[u'python', u'docker-python']) ok: [app2] => (item=[u'python', u'docker-python']) TASK [docker : yum update] ***************************************************** ok: [app1] ok: [app2] TASK [docker : service check] ************************************************** ok: [app1] ok: [app2] TASK [docker : Get installer] ************************************************** skipping: [app1] skipping: [app2] TASK [docker : Install docker-engine] ****************************************** skipping: [app2] skipping: [app1] TASK [docker : Reload systemd] ************************************************* skipping: [app2] skipping: [app1] TASK [docker : Start Docker] *************************************************** skipping: [app2] skipping: [app1] TASK [docker : Join User Group] ************************************************ ok: [app2] ok: [app1] PLAY [all] ********************************************************************* TASK [setup] ******************************************************************* ok: [app1] ok: [app2] TASK [deploy : Deploy Container] *********************************************** changed: [app1] changed: [app2] PLAY RECAP ********************************************************************* app1 : ok=7 changed=1 unreachable=0 failed=0 app2 : ok=7 changed=1 unreachable=0 failed=0
不要な処理がskippingになっていることがみてとれる。
考えるべきこと
そんな感じでひとまず目的を達することはできたが、 実運用を見据えるともっと考えるべきことはある。
Docker Registryを用いたプロキシ
サーバの台数が多くなってくると、 全ての台数分のイメージをグローバルなネットワークから拾ってくるのは現実的ではない。
前にも導入したDocker RegistryをProxyモードで動作させることで一端ネットワーク内部にキャッシングし、 そこから配信するのが良いように思う。
How to Set Up a Registry Proxy Cache with Docker Open Source Registry | Docker Blog
HandlerとNotifyを用いたサービス再起動の明確化
このへんは僕のAnsible力がまだ足りないところ。
tasksの作業がchangedだったか否かを通知して必要最低限のサービス再起動に抑えることができる、らしい。
あと、次のバージョンからはsystemdモジュールが加わるそうなので、サービス周りのコントロールはもう少し上手くできるようになるかも。
Dockerイメージの掃除
今回は不変のイメージの配布なので問題ないが、 実際に更新されるイメージを配布していくとなると、 不要になったイメージは適時削除しないとサーバを圧迫してしまう。
docker-imageモジュールあたりが使えそうだが、まだ調査できていない。
dnsオプションのバグ
docker_containerモジュールで使用するdocker-pyが確認した限りでは最新の1.9.0rc1までdnsオプションの指定がバグっている。
consulなんかを使って内部でDNSを立てるような運用を考えている場合には要注意だろう。
一応/etc/sysconfig/dockerにDOCKER_OPTとして記載するなどの回避策はあるが、ちょっとスッキリしないところ。