読者です 読者をやめる 読者になる 読者になる

年中アイス

いろいろつらつら

DeploymentとServiceをyamlファイルで定義する

kubectlコマンドとオプションでnginxをminikube上で動かしました。 今回は、コマンドで起動内容を指定するのではなく、定義ファイルを使って動かすようにします。定義ファイルにすることでgitで管理できるようになります。

コマンドの振り返り

以下のように起動して、接続できるようにしまていました。

# nginxの起動
kubectl run nginx --image=nginx:1.11 --port=80

# Macから接続できるように
kubectl expose deploy nginx --name=nginx-nodeport --port=80 --target-port=80 --type="NodePort"

これは実際に何をやっているかというと、Deploymentという機能を使ってnginxコンテナを起動し、Serviceを使ってnginxコンテナにアクセスできるようにしています。

Deployment

Deploymentは、複数のコンテナを並列起動し、世代管理できる仕組みです。今回Deployment(+ReplicaSet, Pod)自体が何かというのはあまり重要ではないので省略。以下の記事で細かく解説されています。

qiita.com

Service

Serviceは複数のコンテナへのアクセスを仲介してくれるロードバランサーです。個々のコンテナのアクセス方法を把握しなくても、Serviceの受付口だけ知っていれば、通信できます。

定義ファイル(yaml)にする

定義ファイルはJSONyaml形式が使えます。他のプログラムを仲介する場合はJSONが良さそうですが、設定管理なら個人的にはyamlが好きです。(コメントアウトやコメント書ける点が良い)

nginx-deploy.yml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 2
  template:
    metadata:
      labels:
        run: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.11
        ports:
        - containerPort: 80

kubectl runで指定していたもの+を定義します。

プロパティ 説明
apiVersion Deploymentはベータ開発中なので、extentions/v1betaだそうです *1
kind kubectl runで動かしていたので、隠れていましたが、実態はDeploymentなので明示します。
metadata.name kubectl runの後に指定していた名前
spec.replicas --replicasオプションで指定できる、レプリカ数(コンテナの並列数)
spec.template.
metadata.labels
識別するための任意のKeyValue
spec.template.spec.
containers.name
コンテナの名前
spec.template.spec.
containers.image
--imageオプションで指定していたコンテナイメージ
spec.template.spec.
containers.ports.
containerPort
--portオプションで指定したコンテナの公開しているポート

続いてSerivce。kubectl exposeで指定したもの+を定義します

service.yml

apiVersion: v1
kind: Service
metadata:
  name: nginx-nodeport
spec:
  type: NodePort
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    run: nginx
プロパティ 説明
apiVersion ServiceはDeploymentと異なりv1です。
kind 同様にkubectl exposeを使っていましたがServiceを明示します。
metadata.name --nameオプションで指定していたもの
spec.type --typeオプションで指定していたもの
spec.ports.port --portで指定したいたもの
spec.ports.protocol TCPを明示
spec.ports.targetPort --target-portで指定していたコンテナに対するポート
spec.selector 前述のDeploymentのlabelと関係します。後で詳しく。

起動

kubectl createコマンドで作成(起動)します。

kubectl create -f nginx-deployment.yml
kubectl create -f service.yml

-fは同時に複数指定も可能です。

kubectl create -f nginx-deployment.yml -f service.yml

minikube経由でブラウザ起動して確認。nginxのwelcomeページが見れます。

minikube service nginx-nodeport

DeploymentのlabelとServiceのselector

kubectl exposeコマンドは、その後にdeploy nginxと指定していました。これはnginxという名前 のdeploymentをターゲットにするという意味です。定義ファイルの場合は、labelとselectorで紐付けが行われています。

Serviceのselectorで指定したkey:valueと一致するDeployment*2に、リクエストを流すようになります。 run: nginxを指定していますが、特にrunというkeyにしなくても、Deploymentのlabelをapp: httpserverにして、Serviceのselectorも同様に指定することでもリクエストが流れます。一致すればOKです。

Deployment以外にもlabelやselectorを書けるので、nameよりも柔軟に対象を決めることができるようです。

Labels and Selectors | Kubernetes

いろいろなところで、deploy nginxなど、いちいち名前を書いてられないので、labelをつけて、複数のターゲットを同一label名で識別できるようにすることを目的としているようです。でもlabelは多分もうちょっと詳しく調べないとよくわからず。

参考

*1:http://stackoverflow.com/questions/38547229/apiversion-and-beta-versions

*2:DeploymentじゃなくReplicaSetかもしれない?

Linuxでメモリ使用量が増え続けた

AmazonLinux 2017.03にnginx乗せて稼働させてたら、なぜかメモリ使用量が、1日ちょっとで2GB後半にさしかかりました。cacheではなくusedを徐々に消費してたので、これは何かリークしてる疑い。

プロセス等を見てもメモリを使っているものもいないし、疑いのあるプロセスをrestartしても改善しませんでした。

何だろうなーと調べてみたらすぐ出てきました。特にAmazonLinuxは関係なし。

dev.classmethod.jp

上記ページと同じく、NSSの問題で、curlがdentryというディレクトリ階層構造の管理をしているキャッシュを多く消費してしまうと。curlhttpsの監視が動いており、それが5分に1回なので、徐々にリークした様子。1時間に100MB程度ずつ増加していたようです。

回避策は、NSS_SDB_USE_CACHE=YESという環境変数を指定すること。とりあえず監視スクリプトexport NSS_SDB_USE_CACHE=YESを追加したら、増加が止まりました。

溜まったキャッシュは、以下のようにして解放されました。

echo 2 > /proc/sys/vm/drop_caches

>>でも>でもどっち動きますが多分>の方が適切?2という値は対象のキャッシュによって異なります。

クリアする対象キャッシュ
1 ページキャッシュ
2 dentry,inode
3 ページキャッシュ,dentry,inode

まとめ

じわじわ増えてく系のメモリリークは怖いです。調べたらたくさん出てきたので、割とメジャーな要因みたいですね。

Slabは知らなかったので調べてみたら、ざっくり言うとカーネル側のキャッシュとのこと。細かいメモリ周りはやっぱよくわかってない感。

参考

minikubeで自分の作ったimageを使う

前回minikubeの実行環境を作りました。公開されているnginxのimageを使いましたが、実際は自分でimageを作成し、それを実行させることがほとんどだと思います。minikubeはdockerを内包しているので、そこでdocker buildすればimageが登録され、使うことができます。

nginxに静的ページを追加したコンテナを作って試す

まず、minikubeのdockerを使うために以下を実行します。

eval $(minikube docker-env)

次にお試し用のpage.htmlとDockerfileを作ります。

# page.html
echo '<html>
  <head><title>this is page</title></head>
  <body>original page</body>
</html>' > page.html

# Dockerfile 
echo 'FROM nginx:1.11

COPY page.html /usr/share/nginx/html/page.html' > Dockerfile

ファイル構成としてはこんな形になります。

k8s/  ・・・名前はなんでもよくここにいる状態でeval $(minikube docker-env)
  |- Dockerfile
  +- page.html

imageを作ります。タグはlatestだと動かないので、適当にバージョンつけます。

docker build -t example/nginxpage:0.1.0 .

k8sで動かします。

# コンテナ起動
kubectl run nginxpage --image=example/nginxpage:0.1.0 --port=80

# Macからアクセスできるように
kubectl expose deploy nginxpage --name=nginxpage-nodeport --type="NodePort"

ブラウザで確認します。

minikube service nginxpage-nodeport

標準ブラウザが立ち上がり、nginxのwelcomeページが表示されるので、URL欄に/page.htmlを追加します。 そうすると、original pageと表示され、自分で作ったコンテナが利用できていることがわかります。

参考

ローカルでkubernetesを動かせるminikubeを試す

kubernetes(以降k8s)は、GKE(GoogleContainerEngine)を使うのが簡単らしいですが、とりあえずローカル実行できる環境が欲しくなったりします。

minikubeは、VirtualBoxなどの上にk8sの環境を構築してくれるツールです。今回はとりあえずMac上にk8s環境を作り、nginxを動かしてアクセスするところまでやってみます。

インストール

以下の2つのツールと関連するものをインストールします。

  • kubectl: k8sの操作を行うコマンド
  • minikube: ローカルにk8s環境を作るコマンド

kubectlのインストール

gcloudコマンドでインストール管理できるので、gcloudコマンドを入れます。

ダウンロードページから、MacOS用の、tar.gzをダウンロードし解凍し、install.shを実行します。

tar xzvf google-cloud-sdk-145.0.0-darwin-x86_64.tar.gz
google-cloud-sdk/install.sh

注意点として、Python2.7が必要です。Mac標準なら大丈夫で、何か入れてたら、頑張って2.7にしましょう。pyenv(今env系は何がデファクトなんだろうか)とか入れればできるのかな。

kubectlは、gcloud componentsコマンドでインストールできます。

gcloud components install kubectl

他にもGCP関連のツール群はこのgcloud componentsで入れられます。

minikubeのインストール

今回はVirtualBox上に作るので、先にダウンロードページからdmgを落としてきてインストールします。

次にminikubeです。リリースページにあるコマンドでインストールします。最後にsudo で移動させてるので管理者パスワードを聞かれます。*1

curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.18.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/

k8s環境の構築

構築と書いてますが、以下コマンドで済みます。(が少し時間かかります)

minikube start

k8sのイメージを落としてきて、起動し、kubectlに登録してくれます。終わったら、kubectlで接続先のk8s環境を確認します。

kubectl config get-contexts

CURRENT   NAME       CLUSTER    AUTHINFO   NAMESPACE
*         minikube   minikube   minikube

他の環境がないととりあえずminikubeだけです。GKEとか使うとここが増えます。

nginxを動かしてアクセスする

以下コマンドでnginxコンテナを動かします。

kubectl run nginx --image=nginx:1.11 --port=80

--imageオプションでdockerのimage名とtagを指定します。--portでコンテナが公開しているポートを指定します。

k8s上でコンテナは動き出しますが、これだけだとまだMacからアクセスはできません。以下でにアクセスできるようにします。

kubectl expose deployment nginx --name=nginx-nodeport --port=80 --target-port=80 --type="NodePort"

kubectl expose deploymentの後のnginxは、kubectl runの後に指定した名前(今回はnginx)と同じです。

これでMacからアクセスできるようになるんですが、どのIPとポートなのかわかりません。これはminikubeが直接ブラウザで開いてくれるので、それを使います。

minikube service nginx-nodeport

minikube serviceの後に指定してあるnginx-nodeportは、手前のkubectl exposeで指定した--nameの値です。

これで標準ブラウザが立ち上がり、VirtualBox仮想マシンIPと自動で割り当てられたポートでページが開かれます。今回だとnginxのwelcomeページが表示されます。

URLだけ欲しい場合は--urlオプションをつけます。

minikube service nginx-nodeport --url

# portは自動的に割り当てられます
http://192.168.99.100:31430

なぜMacからk8sのコンテナにアクセスできるのか

とりあえずやるところだけ先に書きましたが、ネットワーク周りをもう少し。今回幾つか手順を踏んで、Macからk8sのコンテナにアクセスできるようになっています。この先の説明に出てくるIPやポートは、今回私が試したものなので、環境によって異なる場合があります。

まず、MacVirtualBox仮想マシン間は、専用のネットワークが構成されて、つながっています。これは192.168.99.0/24です。

k8sは自身のネットワークを構成します。10.0.0.0(サブネットマスクパッとわからず)で、k8sネットワークが作られています。このネットワークは、Macからは直接アクセスできません。

そこで、kubectl expose--type="NodePort"を使って、k8sネットワーク上のアクセス先と、ノード(VirtualBox仮想マシン)をつなげています。今回だとMac<->VirtualBoxネットワークとk8sネットワークをつなげるために、kubectl exposeしているという方が正確です。本来は冗長化した複数のコンテナのアクセス先をまとめるためにkubectl exposeを使います。

--type=“NodePort”によって、VirtualBox仮想マシン192.168.99.100:31430(ポートは自動的に割り当てられる)が、k8sネットワーク上の10.0.0.161:80(IPは自動的に割り当てられる)にフォワードされるようになっています。k8sの方はkubectl get serviceで確認できます。VirtualBoxの方は、前述のminikube service nginx-portnode --urlです。

kubectl get service

NAME             CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
kubernetes       10.0.0.1     <none>        443/TCP        2d
nginx-nodeport   10.0.0.161   <nodes>       80:31430/TCP   27m

アクセスの流れは以下になります。

Mac(VirtualBox network) 192.168.99.1
↓
minikube仮想マシン(VirtualBox network) 192.168.99.100:31430
↓
nginx-nodeport (k8s network)10.0.0.161:80
↓
nginx container(k8s networkのどこかIPとポート)

後始末

起動したものを止める方法

kubectl get service
kubectl delete service nginx-nodeport
kubectl get service

kubectl get deployment
kubectl delete deployment nginx
kubectl get deployment

minikube stop

まとめ

とりあえずローカルで動かすことができました。*2 公開、運用することなどを考えると、GKEが良さそうではありますが、まずは試すという点ではminikubeでもいいのかなと思います。

参考など

*1:特に最後の/usr/local/bin/移動は必須ではないので、その部分を除いて、./minikubeや、別のディレクトリに置いてPATHを通しても良いです。

*2:と言ってもコンテナイメージはdockerから持ってくるので完全にローカルというわけでもないですが

リリースを自動化したら思ったより楽になった

rnzooの0.5.0を開発していくにあたって、機能的な追加の他に、リリースの自動化を行いました。もともとビルド自体はbashスクリプトで自動化していて、dockerを使ったコンテナ上でのビルドとアーカイブファイルの作成を行っていました。

今回、devバージョンをちょこちょこ出しながら進めるために、リリースにかかる手間を減らしたくなったので、少し腰を据えて改善しました。おかげで0.5.0-devは6まで簡単に繰り返しリリースしていくことができました。

以前

実際のリリース(homebrewに反映させる)は、毎回以下をやっていました。

書くとあんまりたいしたことないですが、手作業満載で、頻繁にやらない分、あれどうしてたっけみたいな状態になっていました。

何をやったか

単純にスクリプト化しました。とりあえず動作確認できたら、tag作ってリリーススクリプト実行してリリースされるというところまで。詳細は以下プルリクを参照ください。 https://github.com/reiki4040/rnzoo/pull/20/files

hubコマンドを使って、リリースファイルをアップロード

調べてみたらhubコマンドでできるということで、インストールしてスクリプトに追加しました。

Pull Requestを自動で作成する

こっちに書いたように、関連するプルリクを自動で作成して、最後確認してマージするように。別にPullRequestにせず、masterにそのままpushでもいいんですが、応用が利きそうなので、あえてPullRequestを経由する形にしました。

未解決

テスト

今回homebrewのauditだけ入れていますが、この自動化に際して、hashが間違っていたり、対象ファイルが存在しない(url間違っていた)などを拾ってくれたので、テスト大事ですね。

CI/CDサービスとの連携

まだ自分でキックしないといけないので、自動で開始できるともっと楽になるはず。プルリク作ったら自動テスト実施とか、マージしたらリリースみたいな自然なフローをサポートしてくれる形にしたい。

まとめ

当たり前ですが、自動化すると手間とミスが減るので楽ですね。プロジェクトの最初で足回りちゃんと作るのが後で効いてくるんだなと実感しました。地味地味としたところの突っ掛かりがなくなると、集中しやすくなります。

rnzoo 0.5.0開発中

EC2関連のコマンドをGoで実装したrnzooですが、0.5.0に向けて追加実装を色々やってます。 2017/03/23時点で0.5.0-dev5をリリースしています。devバージョンを使いたい場合は、以下で入ります。

brew tap reiki4040/rnzoo
brew install --devel rnzoo

0.5.0のゴール

大きくは2つです。片手落ちだった機能の拡充と、安全面への配慮。

EC2の作成から削除までライフサイクルを一通りできるようにする

新規作成 -> 停止 -> 起動 -> 停止 -> タイプ変更 -> 起動 -> 削除みたいな流れです。これでAWSコンソール開かなくても起動したりとかの操作は一通りできます。*1

確認をつけてより安全に

使ってもらっている同僚から、確認オプションが欲しいという要望もあり、危なそうなのはデフォルト確認して、確認なしオプションを追加し、動いてもまぁいいかなというのは、デフォルト確認なし、確認するオプションを追加しました。確認もinstance idでは判断できないので、Nameタグを使うようにして、よりわかりやすくしました。

0.5.0-dev5(2017/3/23時点)のアップデート内容

機能追加

変更

  • 実行前に確認オプションの追加(デフォルトは確認なし)
  • 確認時にinstance idではなくNameタグを表示するように
  • attach-eip --movemove-eipサブコマンドとして独立
  • terminate, move-eip, detach-eipはデフォルト確認ありで確認しないオプションを追加
  • サブコマンドの名称からec2を除いた短縮名を追加

internal

  • 使用ライブラリの最新追いつき(aws-sdk-goを最新にしてIPv6対応)
  • release scriptの整備(tag打ったら、リリースして、homebrewのプルリクまで作る)
  • homebrewのdevel対応の為に、バージョン表記からvを削除

リリース予定

アップデート的にはこの辺りにして、3末あたりに0.5.0としてリリースする予定です。次は0.6.0で、SecurityGroup関連と、内部のリファクタあたりをやろうかと。SecurityGroup自体はpiculetでCodenizeするのがいいと思っているので、あくまでEC2への付与、除去と、一時的なルール変更の位置付けになる予定です。

*1:EC2新規作成時のオプションは多すぎるので自分で使うもののみ。他は必要になったら実装する予定。

rnzooにinstance type変更機能を追加しました

rnzooのdevelopバージョンにec2typeというインスタンスタイプを変更するサブコマンドを追加しました。

# すでに入れている場合はtap不要
brew tap reiki4040/rnzoo

# developバージョンの指定でインストール
brew install rnzoo —devel

AWSコンソールからやると1インスタンスずつしかタイプ変更できず、たまに複数やりたくなるので追加しました。複数選ぶときはctrl+spaceです。また、変更したらどうせ起動するというのもあり、変更後に起動させるオプションも-startとしてつけています。

rnzoo ec2typeで、停止中のインスタンスが選択でき、続けてタイプの選択ができます。未指定の場合、以下の小さめ-標準ぐらいのタイプのみが選択できます。

  • t2.nano
  • t2.micro
  • t2.small
  • t2.medium
  • t2.large
  • c4.large
  • m4.large
  • r4.large

最初全部のタイプ選択できてもいいかなと思いましたが、並び順が面倒なのと、xlargeとか高いのは使わないし、間違って起動することを防ぐために除外しました。未指定の選択肢には出てきませんが、-tオプションは自由に指定できます。起動したい場合は、-t c4.2xlargeといった形で変更できます。新しいインスタンスタイプが増えた時に、更新しなくてもとりあえず、-tで対応できるというのもあります。

起動中のインスタンスを停止して変更も考えたんですが、停止まで待つ必要があり、CLIだと数分待てないかなーというのでやめました。多分やろうと思えばec2stop呼んで、ec2listでstoppedになるまで、定期ポーリングして待つとかでできそうですが、実装はしてません。

また、今回から、次のマイナーバージョンのdevelopブランチを作って、そこに機能追加していき、適宜(自分が使いたい時に)developバージョンとしてリリースしていこうとしています。そしてまとまったら正式版?*1としてリリース予定。この辺りはgit ブランチと開発バージョンなどの練習も兼ねてます。

*1:stableとは違うからなんて表現したやら