直近では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件) を見る
DockerまわりについてもDocker Composeの記述と感覚的に近いものがあって馴染みやすかった。
導入
ひとまずは公式ドキュメントに従えば良い。
Installation Guide — 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] 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_serversオプションのバグ
docker_containerモジュールで使用するdocker-pyが確認した限りでは最新の1.9.0rc1までdns_serversオプションの指定がバグっている。
consulなんかを使って内部でDNSを立てるような運用を考えている場合には要注意だろう。
一応/etc/sysconfig/dockerにDOCKER_OPTとして記載するなどの回避策はあるが、ちょっとスッキリしないところ。