2014年8月28日木曜日

[Kubernetes] Kubernetes試す & pods周り




[TOC]

Kubernetes試す & pods周り

今日から当面Kubernetesをやります。 とりあえず今日はGCE上にKubernetesを立ててみます。

Kubernetes is 何?

公式のDesign Docを読んだほうが良いですが、 個々に書いてあるとおりで訳すと以下の感じになります。
原文
Kubernetes is a system for managing containerized applications across multiple hosts, providing basic mechanisms for deployment, maintenance, and scaling of applications. Its APIs are intended to serve as the foundation for an open ecosystem of tools, automation systems, and higher-level API layers.
訳(多分)
Kubernetesは複数ホスト間でコンテナアプリケーションの管理、導入、保守及びスケーリングのメカニズムを提供するするシステムである。そのAPIはツール、自動化及び高レベルなAPI層のオープンなエコシステムの基盤になる。
Dockerを触っていると単体でのコンテナを触る分には特に問題がないのですが、 複数コンテナ、もっと言えば複数ホストをさわろうと思った時に格段に難易度や複雑度が上がります。
例えば複数コンテナではコンテナ間のネットワークや、ホストとのポートフォワードの管理が必要で、 Dockerが提供している機能(例えばlink)だけでは管理が複雑化しやすいです。
さらにサービスをやる上ではスケーリングのために複数ホストで動かすことを考えたくなりますが、 この辺りも複数ホスト上でどおやってコンテナ間を通信させるかなどで悩みどころが多かったりします。 ※複数ホストの場合はAmbassadorsパターンを使って通信させることが多いですね
前者の複数コンテナは現状だと、docker社が採用している「fig」があります。 ただfig自体は複数ホストの管理については機能を提供していません。(多分)
そこでKubernetesのような複数ホスト上で動く、コンテナを管理する仕組が必要になってきます。
色んなベンダーがKubernetesと協業しようとしているので上記だけのシステムななくなってきている感がありますが...

Kubernetes on GCE

とりあえず公式の「Getting started on Google Compute Engine」を読めば行けそうなのでやっていきます。

前提

  • GCEを使える状態にしておく
    • Developer Consoleでプロジェクトを作成して、GCEを使えるようにしておく
  • Google StorageとGoogle Storage JSON APIを有効にしておく
    • 作ったプロジェクトでONにしておく
    • Developer Consoleプロジェクト作成後、 左メニューの「APIと認証」→「API」を押下して、 Google StorageとGoogle Storage JSON APIを探してONにしておく
  • Golangをインストールしておく
  • Google Cloud SDKをインストールして最新化しておく
    • 最新化はインストール後$ gcloud components updateコマンドを叩く
  • gcloudのプロジェクトを設定しておく
    • $ gcloud auth login
    • $ gcloud config set project {作ったプロジェクト名}
  • godepをインストールする
    • $GOPATHを設定し、go get github.com/tools/godep

Kubernetesのインストール

ソースを落としてきます。
 $ git clone https://github.com/GoogleCloudPlatform/kubernetes.git 
$ cd kubernetes

GCEとKubernetesのセットアップ

Kubernetesはsingle master nodeと、worker node (minionと呼ばれている?)と呼ばれるHostが必要です。 Kubernetesのセットアップスクリプトではこれらを自動的にGCE上に立ててくれるスクリプトが存在します。
※以降のシェルは落としてきたkubernetesレポジトリをカレントディレクトリとします。

0. 設定

インスタンスを何台立てるかや、インスタンスタイプの設定はcluster/gce/config-default.shにあります。
以下に全文出しますが、デフォルトから以下を修正しています。
  • ZONE : us-central1-b → asia-east1-a
  • NUM_MINIONS(minionサーバの台数) : 4 → 2
#!/bin/bash

# Copyright 2014 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# TODO(jbeda): Provide a way to override project
ZONE=asia-east1-a
MASTER_SIZE=g1-small
MINION_SIZE=g1-small
NUM_MINIONS=2
# gcloud/gcutil will expand this to the latest supported image.
IMAGE=backports-debian-7-wheezy
NETWORK=default
INSTANCE_PREFIX=kubernetes
MASTER_NAME="${INSTANCE_PREFIX}-master"
MASTER_TAG="${INSTANCE_PREFIX}-master"
MINION_TAG="${INSTANCE_PREFIX}-minion"
MINION_NAMES=($(eval echo ${INSTANCE_PREFIX}-minion-{1..${NUM_MINIONS}}))
MINION_IP_RANGES=($(eval echo "10.244.{1..${NUM_MINIONS}}.0/24"))
MINION_SCOPES=""

1. インスタンスの立ち上げ

設定ができたら、GCEインスタンスを立ち上げます。 この辺りは用意されているスクリプトを走らせれば行けるようです。 ※ちゃんとcleanスクリプトも有ります。
[kubernetes]$ hack/dev-build-and-up.sh
しばらく待つと設定した通りのGCEインスタンスが gcloudコマンドで設定したプロジェクトで起動します。
このdev-build-and-up.shではCloud Storageにmasterとインストールスクリプトとアーカイブを置いて GCEのstartup-script(AWSで言うところのUserDataとかCloud-init)を使って それらを取得、インストールしているようです。 SaltStackもインストールしています。 apiserverなどはSaltStackを使ってインストールしているようですね。

2. コンテナの立ち上げ

2.1. シンプル版
コンテナを立ち上げる前にkubernetesで利用するコマンド群をビルドしておきます。
[kubernetes]$ hack/build-go.sh
上記を行ったらnginxが動くコンテナを2台起動しています。 kubernetesのAPI Serverに対するアクセスは大体cluster/kubecfg.shで行います。
[kubernetes]$ cluster/kubecfg.sh -p 8080:80 run dockerfile/nginx 2 myNginx
起動したら実際にコンテナが起動しているかkubernetes-minion-1にSSHして確認してみます。
[kubernetes]$ gcutil ssh kubernetes-minion-1
[~@kubernetes-minion-1]# sudo docker ps

CONTAINER ID        IMAGE                     COMMAND                CREATED             STATUS              PORTS                    NAMES
0c4ff13d80ed        dockerfile/nginx:latest   "nginx"                39 minutes ago      Up 39 minutes                                k8s--mynginx.55318977--e78ced3e_-_2e6e_-_11e4_-_89af_-_42010af0d357.etcd--8b776ef9
fb598100f220        kubernetes/pause:latest   "/pause"               39 minutes ago      Up 39 minutes       0.0.0.0:8080->80/tcp     k8s--net.97f46b2b--e78ced3e_-_2e6e_-_11e4_-_89af_-_42010af0d357.etcd--988d0fca
346ba49a4b39        google/cadvisor:latest    "/usr/bin/cadvisor" -   3 hours ago         Up 3 hours                                   k8s--cadvisor.1207d44b--cadvisor_-_agent.file--075f2562
ee68b4f0ce61        kubernetes/pause:latest   "/pause"               3 hours ago         Up 3 hours          0.0.0.0:4194->8080/tcp   k8s--net.46426d55--cadvisor_-_agent.file--efd1acc9
nginxのコンテナが起動しているのがわかります。 ちなみにcadvisorは同じくGoogleが開発しているOSSの Dockerコンテナのリソース解析ツール(?)のようでsう。
またpause系のコンテナはよくわかりません(白目 多分netワーク周りを解決するためのコンテナのようです。 ちなみにkubernetesがちゃんと見守っているので、nginxコンテナを削除すると勝手にコンテナが上がります。
[~@kubernetes-minion-1]sudo docker rm -f 0c4
[~@kubernetes-minion-1]# sudo docker ps

CONTAINER ID        IMAGE                     COMMAND                CREATED             STATUS              PORTS                    NAMES
fb598100f220        kubernetes/pause:latest   "/pause"               44 minutes ago      Up 44 minutes       0.0.0.0:8080->80/tcp     k8s--net.97f46b2b--e78ced3e_-_2e6e_-_11e4_-_89af_-_42010af0d357.etcd--988d0fca
346ba49a4b39        google/cadvisor:latest    "/usr/bin/cadvisor" -   3 hours ago         Up 3 hours                                   k8s--cadvisor.1207d44b--cadvisor_-_agent.file--075f2562
ee68b4f0ce61        kubernetes/pause:latest   "/pause"               3 hours ago         Up 3 hours          0.0.0.0:4194->8080/tcp   k8s--net.46426d55--cadvisor_-_agent.file--efd1acc9

[~@kubernetes-minion-1]# sudo docker ps

CONTAINER ID        IMAGE                     COMMAND                CREATED             STATUS              PORTS                    NAMES
479dd3ec30f8        dockerfile/nginx:latest   "nginx"                9 seconds ago       Up 9 seconds                                 k8s--mynginx.55318977--e78ced3e_-_2e6e_-_11e4_-_89af_-_42010af0d357.etcd--d1e4e662
fb598100f220        kubernetes/pause:latest   "/pause"               44 minutes ago      Up 44 minutes       0.0.0.0:8080->80/tcp     k8s--net.97f46b2b--e78ced3e_-_2e6e_-_11e4_-_89af_-_42010af0d357.etcd--988d0fca
346ba49a4b39        google/cadvisor:latest    "/usr/bin/cadvisor" -   3 hours ago         Up 3 hours                                   k8s--cadvisor.1207d44b--cadvisor_-_agent.file--075f2562
ee68b4f0ce61        kubernetes/pause:latest   "/pause"               3 hours ago         Up 3 hours          0.0.0.0:4194->8080/tcp   k8s--net.46426d55--cadvisor_-_agent.file--efd1acc9
なおどのサーバでどのコンテナ(正確に言うとpods)が立ち上がっているかは以下で確認ができます。
$ ./cluster/kubecfg.sh list pods

Name                                   Image(s)            Host                                                  Labels
----------                             ----------          ----------                                            ----------
e78ced3e-2e6e-11e4-89af-42010af0d357   dockerfile/nginx    kubernetes-minion-1.c.bft-all-staff-study.internal/   name=myNginx,replicationController=myNginx
b7fb8713-2e72-11e4-89af-42010af0d357   dockerfile/nginx    kubernetes-minion-2.c.bft-all-staff-study.internal/   name=myNginx,replicationController=myNginx
またちゃんとnginxが起動しているかブラウザ確認したい場合は、デフォルトのままだとGCEのFirewallの8080ポートが開いていないためアクセスできません。
$ gcutil addfirewall --allowed=tcp:8080 --target_tags=kubernetes-minion kubernetes-minion-8080
コンテナを止める場合は以下のようにします。
$ ./cluster/kubecfg.sh stop myNginx
そしてコンテナを削除します。
$ ./clister/kubecfg.sh rm myNginx
2.2. podsファイル版
実際には生のコンテナを直接使うというよりpodsの設定ファイルを利用して立ち上げます。 Kubernetesではある程度サンプルのpodsファイルがあるのでそれを使ってみます。
$ cluster/kubecfg.sh -c api/examples/pod.json create /pods
Podsを確認します。
$ cluster/kubecfg.sh list pods
細かいのはまた別日にやるので削除
$ cluster/kubecfg.sh delete pods/php

GCEインスタンスなどを削除

ひと通り確認が終わったので削除します。
$ cluster/kube-down.sh
これで全ての設定が削除されます

所感

clusteringやコンテナの管理などdockerが自体が提供していないところですが、 実運用で必ず必要になる部分は網羅できている感があります。
単純に1ホスト1コンテナ構成や、1ホスト複数コンテナ構成であればfigとかでもいいのでしょうが、 実際にサービスとかで考えるとkubernetesのようなものは必要になりそうです。
まだ細かい部分バグが有る気がしますが(cluster/kubecfg.sh list podsの時に古いnginxが残ってしまっていた今調べ中) 非常に期待できるシステムな気がします。
次はpods周りを触るのとより深い設定を見て行きたいと思います。

2014年8月27日水曜日

angularでng-annotateを利用してminify対策を自動化する


ng-annotate is 何

ng-annotateとは前回書いたangularJSのminify問題に対応するアプリケーションです。 ソースコードを解析して自動的にdependency annotationを付けてくれます。
ng-annotate適用前
angular
  .module('ngAnnotationTestApp', ['ngRoute'])
  .service('HogeService', function($scope){
    //...
    console.log($scope);
  });
ng-annotate適用後
angular
  .module('ngAnnotationTestApp', ['ngRoute'])
  .service('HogeService', ["$scope", function($scope){
    //...
    console.log($scope);
  }]);
もともと同様のアプリケーションにngminというのがあったのですが 今は、「ng-annotateを使ってくれ」となっています。 ※プロジェクト自体が非推奨になっている
angularを使っている人なら大体使ったことが有るであろうyeomanのgenerator-angularも 現行の最新リリースバージョンではngminを利用していますが、 レポジトリにある最新バージョンはng-annotateを利用するように修正されています。

使い方

ng-annotateはnpmでそれ単体でも利用することができますが、 大抵のタスクランナー(gruntやgulp,browserify)用pluginも出ているようです。
今回はお試しなのでstandaloneで動くバージョンを利用してみようと思います。

インストール

npmでグローバルインストールします。
$ npm install -g ng-anntate
インストールするとng-annotateコマンドが利用できるようになります。

実行

以下の様なsyntaxです。
$ ng-annotate OPTION 
$ ng-annotate -ar -o app/scripts/app.annotated.js app/scripts/app.js
ng-annotateではDependency Annotateの追加/削除を指定することができます。 追加は-a、削除は-rオプションです。両方指定(-ar)すると作り直しになります。
またデフォルトではファイルは作成せず、標準出力への出力になるので、 ファイルに吐き出す場合は-oオプションを使って下さい。
その他のオプションはいい感じで調べて下さい。

対応しているメソッド

ng-annotateは特定のメソッドやコメントを探してコード変換を行います。 対応しているのは以下らへんです。
.controller, .config, .factory, .directive, .filter, .run, .controller, .provider, .service, .animation and .invoke.
またdirective内のcontrollerプロパティ、$routeProvider.when内部、 ui-router等の一部サードパーティ製ツールにも対応が行われているようです。
また明示的に@ngInjectコメントを付与することでも明示的注釈が追加されます。
var x = /* @ngInject */ function($scope) {};

まとめ

ngAnnotateはもともとパフォーマンスが良いように設計されているらしく、 比較的gruntなどで動かしてもそこまで処理に時間がかかりません
angularのminify問題は結構後に気がつくこともあるので、 こういったもので対策を行っておくのも良いかなと思います。
あと今回は触れませんでしたが、ngAnnotateはpluginも作れるので、 コードをパースして何かの処理を挟み込みたい人は使ってみるのが良いと思います。

2014年8月25日月曜日

angular1.3から追加されるng-strict-diを使ってminifyに強くする。

angular1.3から追加される(そう)strict-diモードについて覚書

ng-strict-diとは?

ng-strict-diとはng-app directive と一緒に付加することで、 injectorをstrict-diモードにすることができるng-appのオプションです。

何を解決するか

strict-diモードを利用することで、Dependency Annotationのつけ忘れを防ぎます。

strict-diモードとangularのminify問題

angularのDependency Annotation

AngularJSのDIには大きく分けて3つの注釈が存在します。 AngularJSではこの注釈(annotation)を利用して名前解決をし、Service等のDIを行っています。
  • functionのパラメータ名による暗黙的な注釈
  • $injectプロパティを利用した注釈
  • inline arrayを利用した注釈

functionのパラメータ名による暗黙的な注釈

パラメータ名による暗黙的な注釈は以下のようにパラメータ名を利用してDIを行います。
function MyController($scope) {

} //angularは$scopeという変数名を利用しscopeオブジェクトを代入する

$injectプロパティを利用した注釈

$injectプロパティは注入対象に対してstring arrayとともに付加することで、 名前解決をし、代入を行います。
var MyController = function(renamed$scope, renamedGreeter) {
  ...
}
MyController['$inject'] = ['$scope', 'greeter']; //renamed$scopeにはscopeオブジェクトが代入される

inline arrayを利用した注釈

inline arrayは注入対象をangularに登録する際に一緒に指定する方法です。
someModule.factory('greeter', ['$window', function(renamed$window) {
  // ...
}]);

暗黙的な注釈の問題点

一見暗黙的な代入は記述量も少なく便利なのですが一つ大きな問題があります。 それはminify時に変数名が変わる可能性がある(と言うよりだいたい変わる)点です。
minify前
function MyController($scope) {

} //angularは$scopeという変数名を利用しscopeオブジェクトを代入する
minify後
function A(a) {

} //minifyにより変数名が変わってしまう
このため、minifyをする予定がある場合は後半二つの明示的に注釈する方法が一般的に取られます。 ただ確実にこの注釈を行っているかチェックを行う方法はありません。 というかよく忘れます。

strict-diモード

これらの問題を解決するのがstrict-diモードっぽいです。 strct-diモードはng-appが付いているタグにng-strict-di属性を付けることでstrict-diモードになります。
  <body ng-app="ngStrictTestApp" ng-strict-di>
この状態で暗黙的な注釈を利用している箇所があると以下の様なJSエラーが発生します。
Uncaught Error: [$injector:modulerr] Failed to instantiate module ngStrictTestApp due to:
Error: [$injector:strictdi] function($routeProvider) is not using explicit annotation and cannot be invoked in strict mode
http://errors.angularjs.org/1.3......2) 
これで付け忘れることはなくなりますね。

まとめ

ng-strict-diはngAnnotationのことを調べている時に気が付きました。 よく明示的な指定を忘れて、minifyしてエラーになることが多いので指定しておいて損はない気がしますね。
ただし、最初にも書きましたがAngularJS 1.3からの機能っぽいです。

AnsibleでGCE(Google Compute Engine)を触る

今日はAnsibleでGCEを触りました。
先に参考リンクを貼っていますが、色々見ている通り結構ハマリました。

参考にしたの

環境

  • OS X 10.9.4
  • ansible 1.7
  • apache-libcloud 0.15.1

やってく

やったのは
  • GCEのサーバ2台作って
  • apache入れる
です。
多分上記の参考にした記事を見えればできると思うのですがいくつかハマったのでだらだら書いていきます。

0. ディレクトリ構成

1. apache-libcloud をインストール
pipでインストールします。
$ sudo pip install apache-libcloud

2. CA cert chainの取得

http://curl.haxx.se/docs/caextract.html から
「HTTP from curl.haxx.se: cacert.pem」をダウンロードしておきます。

3. 認証情報の作成

3.1. Compute Engine用の証明書ファイルを作成する

Developer Consoleにて
Compute Engineを使う対象のProjectの「APIと認証」> 「認証情報」から「新しいクライアント IDを作成」を押して、
サービスアカウントとして、新しいクライアントIDを作成
作成後「新しいP12キーを作成」から認証ファイルをダウンロード
ダウンロードしたらpem形式に変換
$ openssl pkcs12 -in {ダウンロードしたp12ファイルパス} -passin pass:notasecret -nodes -nocerts | openssl rsa -out /path/to/pkey.pem
credentialsディレクトリに突っ込んでおく。

3.2. secrets.py (認証情報の設定)

secrets.pyは最初にインストールしたlibcloudが利用する認証情報の設定ファイルで以下の様なフォーマットです。
これをcredentialsディレクトリに入れておきます。
ただAnsibleの公式ドキュメントや、Qiitaの方にはsecrets.pyが書いてあるのだけど、本当に作る必要があるのか疑問があります。
というのもAnsibleのドキュメント上ではこのファイルを作ると、playbook内でmoduleに認証情報を渡す必要がないと書いてあるのですが、
どうもうまくいかない。。。
そもそもこのsecrets.pyは2つの観点で利用されているようです。
  1. gce* moduleがgce apiを叩くための認証情報として利用
  2. Dynamic Inventoryで利用
前者が上記に書いてあるplaybook内でmoduleに認証情報を書かなくて良くなる部分なのですが、
実際に叩いてみると、たとえPTYHONPATHsecrets.pyが置かれているディレクトリが存在したとしても
import secretsが失敗しているようです。
※ansibleのgce moduleでimport secretsしているのは ここ
この編Pythonの知識不足ですね...
もう少し色々やってみますが、、、、
後者は後述します。

4. Inventoryの設定

4.1. ローカルホスト用(hosts)

GCE自体の設定(instanceの設定や、networkの設定)はローカルPCからGCEに対して行うので、
localhostの設定をinventoryに行います。
inventoryディレクトリにhostsファイルを作成し以下のように記述しておきます。



4.2. GCE用のDynamic Inventory
Dynamic Inventoryは動的にInventoryを作成する方法で、任意のスクリプトで、
Inventoryを作成することができます。
例えばGCEの場合instanceを立てるごとにipなどが変わってしまう為、
接続先情報を取得するのに必要です。
GCE用のDynamic Inventoryで利用されるスクリプトは公式レポジトリに置いてあるのでそれを使います。
Ansibleのレポジトリから以下の2つのファイルを持ってきて、
inventoryディレクトリに置いておきます。
  • plugins/inventory/gce.ini
  • plugins/inventory/gce.py
※普通にコピペして持ってくるでOKです。
そして、gce.iniを設定します。
4.2.1. gce.iniの設定
gce.iniでは 3.2. secrets.py で作成したsecrets.pyのパスを指定するか、
secrets.pyに記述したような情報を直接記載するかが選べます。
気になり毎
GCE Dynamic Inventoryを利用するとGCEインスタンスをタグごとにInventoryにまとめてくれたりします。
例えば、4インスタンス(www1,www2,db1,db2)作成してあり、それぞれ2台ずつwebserver(www1,www2)とdbserver(db1,db2)というタグがついていた場合、
GCE Dynamic Inventoryを利用すると tag_webserverというinventoryとtag_dbserverというinventoryを自動的に作ってくれます。
またzoneやrigionごとのinventoryも作成してくれるため、非常にplaybookが書きやすくなります。
ただ、このDynamic Invetoryはansible-playbookが起動する際に読み込まれるようで、
少し問題が有ります。
例えば以下の様なplaybookがあるとします。
  1. 4台のgceインスタンスを作成(tags: webserver, dbserver)
  2. 2台(webserver)にhttpdをインストール
  3. 2台(dbserver)にredisをインストール
この時dynamic inventoryは1.の手前で読み込まれるようです。
つまりまだGCEインスタンスが作成されていない場合は、webserverもdbserverも存在しないため、
2.,3.でtag_webserver inventoryを利用することができないようです。
※playbookが起動する前に、既にGCEインスタンスが作成されている場合は2.,3.で利用することができます。
この辺りはちょろっと微妙だな~と思っています。
実際には2.の手前でDynamic Inventoryを利用してInventoryをリロードして、
2.,3.の工程で利用したいところですが、なんとなーくその辺を探してもなさそうです。
※あったらだれかおしえてくだしぃ
そうなるとインスタンスを立てて、その後ミドルの設定のようなplaybookは2回起動するか、
分ける必要があります。
自前でリロードするmoduleを書けばいいのかもですが...

5. playbookを作成

上記を踏まえて以下の感じになります。
ポイントはDynamic Inventoryを利用してtag_webserverというinventoryを使ってるラ変です。
ただ前述したとおりインスタンスがまだ存在しない場合は、
2回起動しないと思っている状態になりません。
なお
  • "vars/instance.yml"
  • "vars/gce_auth.yml"
は以下の感じです。

6. 起動を作成

あとは普通に起動します。

$ ansible-playbook -i inventory/ master.yml

まとめ

まだまだサンプルが少ないのと、実際に利用されている感じが無いのが厳しいところですね。。。
そもそもGoogle Compute Engine自体がコレ系のセットアップツール(Deployment Manager)をもっているようで(limited previewですが)
この手のサードパーティ製のツールはどこまで対応されるんだっけ?という雰囲気もあるので
仕方ないといえば仕方ないのですが...