※ この記事は2019年3月22日に作成した Kubernetes(GKE)にお安く入門する - LiBz Tech Blog と同じ内容です
はじめに
こんにちは!今回が2回目の投稿になります。LiBのエンジニアの渡邊です!
前回はGitのコミットメッセージにEmoji Prefixを使ってテンションをあげたい話🕺💃🕺💃を書きました。
今回はKubernetes(GKE)について書いていこうと思います!
経緯
自分の所属している部署では開発環境が完全にDocker化されており、プロダクション環境もAWSのECSで運用されています。
オープンソースのKubernetesについても最近の盛り上がりを受けて勉強しておかないとなぁとは思いつつ、なかなか触れることができていなかったので今回のブログを機に入門してみました。
とはいえ個人で使用するためには少々お高くなってしまうKubernetes。
なんとか費用を抑えて、安くHello Worldしたいというのが今回のブログの趣旨となります。
LiBでは1日の業務時間のうちの1時間を、こういった技術習得のための時間にあててもいいという放課後制度があるためエンジニアには嬉しい環境となっています。
(この放課後時間を使ってつくられた社内ツールもあったりします。)
そもそもKubernetesとは
コンテナ化されたアプリケーションのデプロイ、管理、スケール(オーケストレーション)を行うための、オープンソースのコンテナオーケストレーションシステムです。
もともとはGoogleが設計したシステムでGo言語でつくられています。
略記はk8sです。
AWSならEKS, GCPならGKE, AzureならAKSというマネージドサービスがあるのでそれを利用しましょう。
なぜEKS(AWS)ではなくてGKE(GCP)なのか
AWSでは、弊社でも使用しているECSというAmazonが独自に開発したコンテナオーケストレーションシステムがもともとあったため、k8sのマネージドサービスを提供しだしたのはかなり最近です。
(tokyoリージョンだと2018年12月末とか)
もともとGoogle発だったことによるサービスとの親和性や、ドキュメントの数や運用実績を考えると入門するならGCPのGKEが良いと思われます。
あと、GKE自体が無料というところもポイントです。
GKEクラスターを作成するとサーバーはGCEインスタンスとして作成されるのでそこに料金がかかってきます。
しかもGCEインスタンスはUSリージョン限定ですが f1-microインスタンス1つ + 30 GB の HDD が永久に無料です。この無料枠を使わない手はない!
無料のクラスタをつくる
※ GCPアカウントの取得と gcloud , kubectl のインストール・設定は省きます。
gcloud: AWSでいうaws-cliです。コマンドでGCPの操作ができます。
kubectl: k8sクラスタに対してコマンドを実行するためのcliツールです。
とりあえずk8sクラスタをつくってみる
$ gcloud container clusters create <クラスター名> --zone us-west1-a --machine-type=f1-micro --num-nodes=3 --disk-size=10
--num-nodesがインスタンスの数です。
--num-nodes=3
で3つもインスタンスたてたら無料じゃないじゃんってなりますが、f1-microだと最低3インスタンスないとエラーになるので今はおとなしく3つ立てます。
実はf1-microでクラスタつくるときは最低3つ必要なのですが後から消すということができます。
ちなみに --zone を指定しないとマルチゾーンで作られてしまうので要注意。
us-west1-a, us-west1-b, us-west1-cにnum-nodesの数だけインスタンスができちゃいます。
$ gcloud container clusters list
で 確認してみるとNUM_NODESが3になってるはずです。
ノード(インスタンス)が1つだけのノードプールをつくる
ノードプールとはインスタンスのグループのことです。
cluster作成時につくられた3つのノードは default-pool
という名前でグループ化されています。
これとは別に1ノードのみのノードプールをつくり、あとからdefault-poolを消すことでサーバの数を1台だけします。
$ gcloud container node-pools create <プール名> --cluster <クラスター名> --zone us-west1-a --machine-type=f1-micro --num-nodes=1 --disk-size=10
3つのノードが登録されているノードプールを消す
$ gcloud container node-pools delete default-pool --zone us-west1-a --cluster <クラスター名>
default-pool
を消しました。
$ gcloud container clusters list
でノードの数を確認すると1になっています。
とりあえずこれで無料にはなりました!!
はたして最低スペックで動くのか...
作成したクラスタに接続
$ gcloud container clusters get-credentials <クラスター名> --zone us-west1-a
上記のコマンドを実行するとクラスターが設定ファイルに登録され、kubectl コマンドでクラスターを操作できるようになります。
ちなみに設定ファイルはデフォルトだと ~/.kube/config
です。
起動したクラスタのPodを確認してみる
podとは?
node(インスタンス)の中で動いているdockerコンテナ、みたいなイメージです。
面白いのは、k8sは node全てのパワーを全体リソース として、podがいくつも立ち上がります。
f1-microのnodeだとメモリが 0.6GB なので、nodeが3つあれば1.8GBのメモリを使用でき、どのnodeでどのpodを起動するかとかは全ていい感じに自動でやってくれます。
あるnodeにはpodAとpodBが1つずつ、あるnodeにはpodBが3つ、といったようにnode全体としてpodsを共有しています。
pod = dockerコンテナ みたいなことを言いましたが pod1つの中に、任意の数のdocker containerを詰め込むことも可能 です。nginxとapllicationとredisとか。
pod 1 つに対して内部IPが 1 つ割り当てられ、コンテナ間で共有できるvolumeも任意の数を持つことができます。
kubectlコマンドでpodの状態を見てみる
$ kubectl get pod --all-namespaces
--all-namespaces
をつけると、k8s-systemが動いているdefaultのpod含め全てのpodの状態をみることができます。
(オプションつけないと自分で立ち上げたpodだけ見れます)
pendingが2つあります...やはりスペックが足りなくてデフォルトのpodすら立ち上がらない模様。
念の為原因を確認します。
$ kubectl describe pod --namespace kube-system kube-dns-548976df6c-hf67f
(長いので省略)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 42s (x8080 over 20h) default-scheduler 0/1 nodes are available: 1 Insufficient memory.
「Insufficient memory」 メモリ不足でした!\(^o^)/
preemptibleインスタンスのnodeを立ててスペックを確保する
preemptibleインスタンスとは?
Google の膨大なデータセンターの余剰リソースを活用したインスタンスです。(要するに余ってるサーバー)
普通のインスタンスとなんら変わりはありませんが、 必ず24時間以内にシャットダウンされます。
そのため、通常のインスタンスの価格の数分の一の価格で提供されています。(最大70%オフ)
\(^o^)/ここから有料です\(^o^)/
preemptibleインスタンスが1つのnode-poolをつくってみる
$ gcloud container node-pools create <プール名> --cluster <クラスタ名> --zone us-west1-a --machine-type=f1-micro --num-nodes=1 --disk-size=10 --preemptible
しばらくたってからpodの状態を確認
全てrunningになった!!
GKEを動かそうとおもったら最低でもf1-microインスタンス2つ相当のスペックがいるようです。
Datadogで監視してみる
「ところでpreemptibleインスタンスってほんとに1日に1回死んでるの?」
これを確認するために監視ツールのDatadogを入れてみます。
Datadogは5台以下のホスト構成ならFreeプラン(無償)でモニタリングしてくれます。(データの保管期間は1日)
Datadog公式でKubernetesへのDatadog Agent導入方法が用意されているのでただ導入するだけなら簡単にできます。
Datadogにログインしている場合はこちらに書いてある datadog-agent.yaml
そのままコピペで作成してcreateできます。
# 導入方法に従ってdatadog-agent.yamlを作成
$ vi datadog-agent.yaml
$ kubectl create -f datadog-agent.yaml
daemonset.extensions "datadog-agent" created
daemonsetで datadog-agentがcreateされました。
ReplicaSetとDaemonSet
ReplicaSetとは...
前述のnode全てのリソースを全体リソース として、podが立ち上がるための仕組みです。
(クラスタ上のPodを決められた数に維持してくれる仕組み。)
例えば、Aノードが死んでその上で動いていたコンテナ(Pod)が減っても、Bノードでまた同じ数のPodを起動してくれます。
DaemonSetとは...
1ノードに1つだけPodを置いてくれる仕組みです。
例えば、ノードAでPodに異常が出て死んでしまっても、同じAノード上にPodが蘇るように調整してくれます。
クラスタにBノードが追加されたらそのノードにも自動でPodを配置してくれます。
DatadogやMackerel等でのコンテナクラスタの監視はこのDaemonSetでコンテナを建てるようです。
Datadogをいれたらまたスペックがたりないと言われた、、
前述の通りDatadogはDaemonSetなのでノードを増やしたところでスペック不足は解消されません。
ノード自体のスペックを上げるしかないです、、
無料枠の活用は諦めてg1-smallのpreemptibleインスタンス2台構成にしました(T_T)
# めんどくさいので1度全て削除してからもう一度作成
$ kubectl delete -f datadog-agent.yaml
$ gcloud container node-pools delete <preemptibleプール> --zone us-west1-a --cluster <クラスタ名>
$ gcloud container node-pools delete <無料プール> --zone us-west1-a --cluster <クラスタ名>
# g1-smallのpreemptibleインスタンスのノードプール作成
$ gcloud container node-pools create <プール名> --cluster <クラスタ名> --zone us-west1-a --machine-type=g1-small --num-nodes=2 --disk-size=15 --preemptible
$ kubectl create -f datadog-agent.yaml
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
datadog-agent-77lwl 1/1 Running 0 4m
datadog-agent-tn4cs 1/1 Running 0 4m
ようやくDatadogのダッシュボードも見れるようになりました!
nginxを立てる
続いて、nginxを動かしてみます。
nginx.deployment.yamlを作成
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.5
ports:
- containerPort: 80
deploymentを作成する
$ kubectl create -f nginx.deployment.yaml
deployment.extensions "nginx" created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
datadog-agent-77lwl 1/1 Running 0 10m
datadog-agent-tn4cs 1/1 Running 0 10m
nginx-64c6b46884-zc9mq 1/1 Running 0 1m
続いてこのnginxにアクセスしたいのですが、この状態ではポートが外部に公開されていないためアクセスできません。
公開する方法としては、ロードバランサを用いる方法があるのですがロードバランサは高いです!!!!
今回は安く抑えるためロードバランサを用いず、ノードのポートを直接公開します。
nginx.service.yamlを作成
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
externalIPs:
- <ノードの内部IP>
- <ノードの内部IP>
<ノードの内部IP>
となっている箇所は適時書き換えてください。
内部IPはGCPのコンソールでインスタンス内部IPを見るか、下記のコマンドで取得できます。
$ gcloud --format json compute instances list | jq -r '.[].networkInterfaces[].networkIP'
serviceを作成
$ kubectl create -f nginx.service.yaml
service "nginx" created
最後にノードのポートを開放します。
$ gcloud compute firewall-rules create http-firewall --allow tcp:80
..省略..
Creating firewall...done.
NAME NETWORK DIRECTION PRIORITY ALLOW DENY DISABLED
http-firewall default INGRESS 1000 tcp:80 False
これでOKです!
あとはノードの外部IPにアクセスすればnginxのデフォルトページが表示されるはずです!!
外部IPもGCPコンソールで確認するか下記のコマンドで各自調べてください。
$ gcloud --format json compute instances list | jq -r '.[].networkInterfaces[].accessConfigs[].natIP'
🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉
nginxのdeploymentとservice2つ作ってたけど何?
Deploymentとは?
複数のコンテナを並列起動しバージョン管理できる仕組みです。
Deploymentは前述したReplicaSetを生成・管理し、ReplicaSetはPodを生成・管理します。
Kubernetes: Deployment の仕組み - Qiita
Serviceとは?
Serviceは複数のコンテナへのアクセスを仲介してくれる仕組みです。
Serviceのおかげで個々のコンテナのアクセス方法を把握しなくても、Serviceの受付口だけ知っていれば通信できます。
Kubernetesの Service についてまとめてみた - Qiita
アプリケーションをデプロイする
せっかくなので自分でつくったアプリケーションをデプロイしたいところですが、
今回はGKE自体を使うことが目的なのでGoogleが公式で用意してくれているHelloWorldを表示するだけのアプリケーションをデプロイします!
ちなみにこのhello-appはGCR(GCPのコンテナレジストリ, AWSでいうECR)に用意されているのでそのままpullして使えます。
GCRのリポジトリはこちら
次にnginxと同じように hello.deployment.yamlとhello.service.yamlを作成します。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: hello-app
labels:
app: hello
spec:
replicas: 1
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
spec:
containers:
- name: hello
image: gcr.io/google-samples/hello-app:1.0
ports:
- containerPort: 8080
apiVersion: v1
kind: Service
metadata:
name: hello-app
spec:
type: NodePort
selector:
app: hello
ports:
- name: http
port: 8080
targetPort: 8080
externalIPs:
- <ノードの内部IP>
- <ノードの内部IP>
$ kubectl create -f hello.deployment.yaml
$ kubectl create -f hello.service.yaml
# firewallで8080番も許可
$ gcloud compute firewall-rules create hello-firewall --allow tcp:8080
あとはノードの外部IPの8080番にアクセスすれば**「Hello, World!」**です!
🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉
nginxで80 -> 8080のプロキシの設定だけやってみる
アプリケーションとnginxのが動きだしたので、nginx.confで色々設定したくなってきました。
今回はお試しなので80番ポートにアクセスがきたら8080番に飛ばすプロキシの設定だけやってみます。
kubernetesで動かすDockerコンテナ内にどうやって設定ファイルを差し込む?
今回でいえばnginx.confなどの設定ファイルをどうやって管理するか、です。
方法はいくつかあります。
1. Dockerイメージの中に入れておく
- ADDした状態でdocker buildしておく
- buildしたコンテナをコンテナレジストリにpushしておく
2. ファイルにしておいてコンテナにVolumeとしてマウントする
- デプロイするホストのディレクトリをマウントする
- 永続ディスクを作成してそこに配置しておいてマウントする
3. どこかのストレージに置いておいてダウンロードする
今回は勉強のためにkubernetesのConfigMapというものを使ってみようと思います。
ConfigMapとは、簡単にいうとkey-valueで値を持てるものらしいです。
イメージ的には環境変数に近いですが、設定ファイルも持つことが出来ます。
volumeとしてもマウントもできるようになっています。
nginx.config.yamlを作成
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-conf
data:
nginx.conf: |
user nginx;
worker_processes 3;
error_log /var/log/nginx/error.log;
events {
worker_connections 10240;
}
http {
log_format main
'remote_addr:$remote_addr\t'
'time_local:$time_local\t'
'method:$request_method\t'
'uri:$request_uri\t'
'host:$host\t'
'status:$status\t'
'bytes_sent:$body_bytes_sent\t'
'referer:$http_referer\t'
'useragent:$http_user_agent\t'
'forwardedfor:$http_x_forwarded_for\t'
'request_time:$request_time';
access_log /var/log/nginx/access.log main;
server {
listen 80;
server_name <ノード1の外部IP>;
location / {
proxy_pass http://<ノード1の外部IP>:8080/;
}
}
server {
listen 80;
server_name <ノード2の外部IP>;
location / {
proxy_pass http://<ノード2の外部IP>:8080/;
}
}
}
$ kubectl create -f nginx.config.yaml
configmap "nginx-conf" created
あとはconfigmapをnginxのdeploymentに適用します。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.5
ports:
- containerPort: 80
volumeMounts:
- mountPath: /etc/nginx # /etc/nginxにvolumesのnginx-confをmountする
readOnly: true
name: nginx-conf
- mountPath: /var/log/nginx
name: log
volumes:
- name: nginx-conf # volumeMountsで/etc/nginxにmountするやつ
configMap:
name: nginx-conf # ConfigMapのnginx-confを/etc/nginx以下に配置する
items:
- key: nginx.conf # nginx-confのkey
path: nginx.conf # nginx.confというファイル名
- name: log
emptyDir: {}
では実際にnginxコンテナの中に入ってnginx.confがマウントされているか見てみます。
# kubectl get pod でnginxのコンテナ名を調べて中に入る
$ kubectl exec -it <nginxのコンテナ名> bash
# nginx.confをみてみる
$ cat etc/nginx/nginx.conf
etc/nginx/nginx.conf
がConfigMapで設定したものになっているはずです!!
実際に80番ポートでnginxにアクセスするとhello-appのHello, Worldが表示されました!
最後に
いかがでしたでしょうか?
今回はあくまで個人での検証/実験のためにつくっているので、本物のサービス運用を見据えたものとは全く違うと思います。
細かい仕様や制限についてもまだまだ調べきれていないので、考慮漏れや間違いなどが多々あると思うので、そこは注意していただきたいです。
料金に関しては、今回はDaemonSetでDatadogをいれたので無料枠のf1-microを諦めましたが、監視ツールをいれなければもっと安くk8s環境を構築できそうですね。
また、GCPは登録から12ヶ月は300$分を無料で試せるのでよっぽど贅沢な設定にしなければ財布には優しいかと思います。
昨今のマイクロサービスブームやGoogle Cloud Next ’18 でk8s上でServerlessを実現するKnativeが発表される等、今一番Hotといっても過言ではない Kubernetes。
エンジニアとしてはこういった新しい技術はどんどん触れたいですね!
今回作成したyamlファイルはこちらのリポジトリにまとめました。