Ansible 2.0を使って組む kubernetesクラスタ vol.1
-
Upload
hidetoshi-hirokawa -
Category
Technology
-
view
7.360 -
download
5
Transcript of Ansible 2.0を使って組む kubernetesクラスタ vol.1
Ansible 2.0を使って組む kubernetesクラスタ vol.1
Ansible Meetup in Tokyo 2015.09 廣川英寿@realglobe Inc.
自己紹介
• 廣川英寿
• Github: h-hirokawa
• 技師@realglobe Inc. (2011.9~)
• メイン言語はPython、とりわけDjango
• ansibleは2012末から利用中
ansibleとコンテナ周り 結構やってますよアピール
• NiftyCloud C4SA (2012) • ウェブアプリ開発+運用環境構築PaaS • LXCコンテナベース
• C4SA エクスポート機能 (2013) • コンテナ上に展開されたアプリケーション環境をIaaSに移行 • 移行機能はpure-ansible
• Deplow (2015) • AaaS (Ansible as a Service) • Dockerコンテナで切り分けられたAnsible環境と管理UIを提供
• 毎月の無料Ansible勉強会主催やCI導入支援、プレイブック代書もやってます
Kubernetesって何者?
Kubernetesの説明を駆け足で
Kubernetes(略称: k8s)とは• Google主導で開発された、コンテナ用クラスタ管理ツール
• コンテナ群を用いた大きな実環境の運用を実現
• やってくれること
• コンテナを組み合わせてのアプリケーション環境構築
• Pod: 1アプリケーションを構成するコンテナの組
• Rep. Controllers: Podを冗長構成で稼働。死活監視付きで自動増減する。
• Service: 冗長化されたPodに接続を振り分けてくれるLB的なもの
• ノードのクラスタ管理
• ノード間コンテナ通信
やりたい事• Ansible v2でk8sクラスタを自動構築する
• CoreOSで作る
• 複数IaaS対応(今回はgce & idcf)
• 1クラスタを複数基盤にのせる • 非推奨パターンなので今回はやめました • クラスタ間の接続をPrivate IPからGlobalに変えれば可能
CoreOSとは?• コンテナ稼働専用の軽量ディストリビューション
• etcd, fleet等、k8sのクラスタリング基板が組み込み済 • いずれもCoreOS社がオープンソースで開発 • rktなどのDockerに代わるコンテナ基盤も作っている
• コンテナ専用だから、/usr 以下はReadOnly
• コンテナ専用だから、パッケージマネージャも無い
極め付けに
Pythonが入っていない!
Pythonないのに何故Ansible?• Ansibleには rawがある
• SSHログイン・シェル上で直接コマンドを実行
• scriptモジュールでも同様に生スクリプトを実行できる
• モジュールがどんな言語でも書ける
• SSHで繋がる以外に事前準備は「一切」不要
Pythonがホストになくたって自動化できる そう、Ansibleならね
とは言ったものの…• 全部rawでやるのはキツい
• 返り値を直接読んで処理しなければならない
• rc, stdout, stderror
• 冪等性を守るも守らないも自分次第
• register, whenの嵐で見通しも悪い
• v2のblockディレクティブである程度は緩和出来る
• いっそscriptで全部やっちゃう?
• あれ、何でAnsible使ってるんだっけ
そんなあなたにPyPy
PyPyとは
• RPythonで書かれたPythonの実装系(かっこいい)
• JITコンパイルされるのでCPythonより高速(かっこいい)
• 任意のパスで実行可能なバイナリが配布されている(かっこいい)
* RPython: バイナリコンパイル可能な制約付きPython
CoreOSにPyPyを入れるメリット• いつも通りにAnsibleが使えるようになる
• pipや他のpythonパッケージも問題なく使用可能
• どのAnsibleモジュールでも問題なく動くかは未確認
• とは言え、CPythonとの違いはGCの方式などのプリミティブな所なので、ほとんどの場合問題ないはず
• /opt等に置いてそのままCoreOSホスト上で動かせる
• バイナリパッケージを配置するだけなので、デプロイが短時間で済む
CoreOS上でPyPyを動かす注意点• ansible_python_interpreterでpypyへのパスを指定する必要あり
• pypy/lib/libtinfo.so.5 => /lib64/libncurses.so.5.9 のsymlinkが必要
• PyPy 2.4.0を使う
• CPython 2.7.8互換
• 最新のPyPy 2.6.1だと現段階では動いてくれない
• “no version information available” 警告が出てしまうのは諦める
Let’s playbook!
Playbook
今回作ったサンプルをgithubで公開してます
https://github.com/h-hirokawa/ansible-kubernetes-coreos
方針
1. IaaS上にCoreOSノードを作成
2. CoreOSにPyPyをインストール
3. CoreOSにkubernetesをインストール
• master: kubernetesのAPIを立てる(今回は1台)
• minion: 各コンテナを稼働させるノード(複数台)
tipsやハマりどころを解説
1. IaaS上にCoreOSノードを作成
~Ansibleからの効率的なIaaS操作~
• これはCoreOSに限った話ではないですが、御存知の通りAnsibleからはデプロイ対象ホストの操作に留まらず、IaaSの操作も簡単に行えます。
• 組み込みクラウド・モジュール例
AWS, Azure, Cloudstack,
Google, Openstack, Vmware…
ただし、注意が必要なポイントも
• IaaS操作は、localhostからクラウドのAPIを操作する事になります
ということは、
並列化が効かない!
• 何も気にせずに複数台にまたがるIaaS操作を実行すると、1台1台のVMインスタンス作成リクエストからVM作成(起動)完了までが同期で進んでしまうため、APIを叩いて待つだけの操作に大幅な時間がかかってしまいます。
• 実はほとんどのクラウド操作モジュールには、操作完了までの待機をせず、APIリクエストを送った時点で次のタスクに進んでくれるオプションがあります。
各モジュールとパラメータの対応
• ec2
• wait: no
• azure
• wait: no
• cs_instance
• poll_async: no
• gce
• なし…
• os_server
• wait: no
• vca_vapp
• wait: no
CloudStackの場合- name: まとめてインスタンス作成リクエスト cs_instance: name: "{{ item }}" hypervisor: VMware template: MyOwnImage service_offering: xLarge ssh_key: ssh-key poll_async: no # ここが肝 with_items: [ vm1, vm2, vm3 ]
## 必要ならここで待っている間の処理
- name: インスタンス作成完了するまで待つ cs_instance: name: "{{ item }}" with_items: [ vm1, vm2, vm3 ]
gceの場合
• 待機制御用のパラメ−タが無いけれど、どうするの?
• 自分でカスタム・モジュール作れば良いじゃん
そうだ、asyncがあった!
asyncを使った非同期タスク- name: 非同期処理開始 command: sleep 60 async: 100 # 最大待機時間。pollが0の場合は正の数であればなんでも良い poll: 0 # ポーリング間隔。タスクを非同期に先に進める場合は0 register: async_job
## 間の処理
- name: 非同期処理終了待機 async_status: jid: "{{ async_job.ansible_job_id }}" register: job_result until: job_result.finished # ジョブ終了までループ retries: 30 # 最大試行回数 delay: 10 # 試行間隔
asyncを使うと
• 時間がかかる処理を非同期で実行可能
• 各非同期タスク毎に個別プロセスが立つ
• poll を 0 に設定すると、タスクを実行した後、即時タスクを次に進められる
• 非同期実行しているタスクの結果は async_status 経由で取得が可能。
v2ではループも使える- name: 複数ジョブを同時実行 command: sleep 60 async: 100 poll: 0 with_sequence: count=5 register: async_jobs
- name: ループで全ジョブの終了を待機 async_status: jid: "{{ item.ansible_job_id }}" register: jobs_result with_items: "{{ async_jobs.results }}" until: jobs_result.finished retries: 5 delay: 30
asyncでgceインスタンス作成- name: 非同期インスタンス作成 gce: instance_names: vm-name machine_type: g1.small image: coreos async: 1000 poll: 0 register: create_instances_job with_items: [ vm1, vm2, vm3 ]
- name: インススタンス作成終了待機 async_status: jid: "{{ item.ansible_job_id }}" register: job_result until: job_result.finished retries: 30 with_items: create_instances_job.results
これを実行すると!
動かない…
TASK [非同期インスタンス作成] ************************* ok: [localhost] => (item=vm1) ok: [localhost] => (item=vm2) ok: [localhost] => (item=vm3)
TASK [インススタンス作成終了待機] ********************* fatal: [localhost]: FAILED! => {"failed": true, "msg": "ERROR! The conditional check 'job_result.finished' failed. The error was: ERROR! error while evaluating conditional: job_result.finished ({% if job_result.finished %} True {% else %} False {% endif %})"}
• 下記のようなstderrがjobの実行結果ファイルに含まれているのが原因
• 出力をJSONとしてパース出来ないためエラー
• ansibleのバグか?
• stdout、stderrを別ファイルに吐くようにするのが望ましいと思われる
• モジュールがstderrを吐いていること自体微妙なので、今回はログハンドラーを追加したgce-modをplaybook中に含める対応をとった
No handlers could be found for logger "libcloud.common.google"
1. まとめ
IaaSのインスタンス操作には
非同期処理を上手く使おう!
2. CoreOSのセットアップ①
~Ansible v2での非Pythonモジュール~
Ansibleモジュールの自由度• Ansibleの大きな特徴の一つが、どんな言語でも
1. shebang(#!/bin/bashなど)でインタプリタ指定可能、もしくは実行可能なバイナリに事前コンパイル可能
2. JSONで入出力可能 • 入力: モジュールに与えられた引数 • 出力: Ansibleが解釈する実行結果
上記条件を満たせば、ansibleモジュールに出来る所です
raw_stat モジュール• 今回はPyPyインストールの手順中で複数回実行される、「あるパスが存在するかどうか」を判定するモジュールをShellScriptで書いてみました
• statモジュールの入出力を意識して、raw_statとしていますが、今回実装しているのは最低限必要な存在チェックのみ
• 因みに、ShellScriptでのJSON操作には jq コマンドが便利 • 最近のCoreOS(717.0.0以降)ではjqが組み込まれてます
raw_stat#!/bin/bash # WANT_JSON
ANSIBLE_VERSION="<<ANSIBLE_VERSION>>"
# jqがなかったらエラー if ! type jq >/dev/null 2>&1; then echo "{\"failed\": true, \"msg\": \"jq not found.\"}" exit 1 fi
# v1の場合は$1に引数ファイルのパスが入る if [[ $ANSIBLE_VERSION == 1.* ]]; then MODULE_COMPLEX_ARGS=$(cat $1) # v2の場合はINCLUDE_ANSIBLE_MODULE_WINDOWS_ARGSを使う else MODULE_COMPLEX_ARGS=$(cat << 'EOF' <<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>> EOF ) fi
path=$(echo $MODULE_COMPLEX_ARGS | jq -r '.path') if [ -e $path ]; then exists="true" else exists="false" fi
echo $MODULE_COMPLEX_ARGS | jq -r ".changed=false | .stat.exists=$exists"
raw_stat解説• 実はAnsible v2では、現在の公式ドキュメントで解説されている、モジュールへの引数を「hoge=fuga foo=bar」の様なスペース区切りファイル経由で渡す方法は使われなくなっています
• 代わりに、モジュールファイル内のリプレイサー文字列を経由して引数を埋め込む形式を使わなければなりません • リプレイサー文字列は、template モジュールでの変数と同じ様なものだと考えてください
リプレイサー文字列• <<ANSIBLE_VERSION>>
• 使っているAnsibleのバージョンで置換される
• v1でも使えるので、v2との互換モジュールで必要
• <<INCLUDE_ANSIBLE_MODULE_WINDOWS_ARGS>> • モジュール引数がJSONとして置換される
• 引用符などもエスケープされていないので、heredoc経由で読む様にするなどの工夫が必要
• 最新develブランチでは <<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>> も使える(プルリク出したら通りました)
• 他にもあるが、とりあえず非Pythonモジュールで使うのは上記2つ位
v1では WANT_JSON を使おう• これもドキュメントでは触れられていないが、v1では、モジュール内に WANT_JSON と言うコメントを入れると、引数ファイルの形式がスペース区切りからJSONに代わる
• この形式を使うと、v1とv2での引数処理がJSONの処理として同様に書ける。
• そもそも、元のスペース区切り形式だと複雑な文字列処理がとても厄介(場合によっては対応不能)なので、積極的に WANT_JSON を使って行くのが良いと思います
hoge=fuga foo=bar #これが {"hoge": "fuga", "foo": "bar"} #こうなる
2. まとめ• Ansibleでのモジュール実装は言語を問わない
• v2で非Pythonモジュールに引数を埋め込むには <<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>> を使う
• v1-v2互換モジュールを作るには <<ANSIBLE_VERSION>> で場合分け
• v1でも WANT_JSON を積極的に使っていこう
3. CoreOSのセットアップ②
~Cloud-Config with Ansible~
Cloud-Configとは• IaaS上のインスタンスの各種設定変更やサービス起動などを起動時に自動で実行するための仕組み
• 設定ファイルはyamlで書く(やったね)
• CoreOSでは専用の coreos-cloudinit コマンドが使われる
#cloud-config coreos: units: - name: "etcd2.service" command: "start" - name: "fleet.service" command: "start"
k8s用のCloud-Config
• k8sの公式リポジトリ内で、master用のmaster.yaml、minion用のnode.yamlが提供されており、ほぼそのまま使える
• サンプルプレイブックでは、node.yaml中で使うmasterのIPアドレスなどを変数化したテンプレートにしました
Cloud-Configの問題点
• 設定が正常に完了したかを確認するのに別の機構が必要
• ベンダー毎に設定の渡し方が違う
• ec2: UserDataにyamlを入れる
• gce: Metadata中のuser-dataキーにyamlを入れる
• idcf: UserDataにyamlを入れられるが2KB制限あり
2KBじゃ足りない…
全部Ansibleでやっちゃおう
• 折角Ansibleを使っているんだから、Cloud-ConfigでやっていることもAnsibleで置き換えれば、どんな環境でもセットアップ可能になるはず!
やってみた結果
• 1つのyamlが10以上の設定ファイルテンプレートに増殖
• Cloud-Config上の設定項目と実設定ファイルの対応は、coreos-cloudinitのソースを見ないとわからない
• かなりの苦行な上、プレイブックの見通しも悪くなってしまった
既存の資産はうまく使うべき
結論: いいとこ取り• 最終的に、ブート完了後にAnsibleでログインしてCloud-
Config用のyamlをCoreOS内に設置し、Ansibleからcoreos-cloudinitを実行する様にした
• Ansibleがyamlを配置するので、ベンダーの実装を気にする必要なし
• coreos-cloudinit実行後のサービス正常起動確認まで、まとめて実施可能
master用playbook--- - name: Set cloud-config.yml. template: src: master.yml dest: /opt/cloud-config.yml register: set_cloud_config
- name: Run cloudinit. command: /usr/bin/coreos-cloudinit -from-file=/opt/cloud-config.yml when: set_cloud_config|changed
- name: Start required services. service: name: "{{ item.name }}" state: started with_items: - name: generate-serviceaccount-key.service - name: setup-network-environment.service - name: fleet.service - name: flanneld.service - name: docker.service - name: kube-apiserver.service - name: kube-controller-manager.service - name: kube-scheduler.service
3. まとめ
• AnsibleからCloud-Configを扱うことで、IaaSベンダーの制約を気にしないで良くなった
• Ansibleなら動作確認までをワンストップで実施できる
• 「Ansibleだけで」にこだわる必要は無い。どんなツールや手法とも上手く連携出来るのがAnsible
今後やりたいこと• minionノードのオートスケールをAnsibleから組む
• オートスケール操作はgce系モジュールでは未提供
• k8sの操作自体をAnsibleから実施できるようにする • kubectlかkubernetes-apiを操作するモジュールが必要
• k8s内のコンテナ内をAnsibleから操作可能にする • v2で登場したdocker connection plugin を kubectl
execコマンドとつなげることができるか
vol. 2に続く?
ご静聴ありがとうございました
Ansible本 年内発表予定