Kubernetesのマニフェストの解説
Namespace
クラスタ内に1つの名前空間を作ります。
リポジトリごとにNamespaceを最低限切るようにすると、他のアプリケーションとバッティングすることなく、明確にリソースを区切るので事故も起こりにくいです。
apiVersion: v1 kind: Namespace metadata: name: myapp # myapp という名前の namespace を定義
ConfigMap
nginx.confなどの設定ファイルは、ConfigMapを使ってそのファイルごとにKuberntes上に保存し、コンテナ起動時にマウントするようにすると、柔軟に設定が変更できます。
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-conf
namespace: myapp
data:
nginx.conf: |
user nginx nginx;
worker_processes 1;
error_log /dev/stdout notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main; # dockerの公式nginxは、この path に log を出力すると stdout に表示する用になっているため
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
upstream app {
server unix:///app/tmp/sockets/puma.sock fail_timeout=0; # unix domain socket を使って通信
}
server {
listen 80;
server_name example.com;
root /app/public; # 静的ファイルを /app/public で配信し、それ以外は、Rails サーバーに問い合わせ
try_files $uri/index.html $uri @app;
location @app {
proxy_pass http://app;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
}
error_page 500 502 503 504 /500.html;
client_max_body_size 4G;
keepalive_timeout 10;
}
}
- k8stailでログを確認する
$ k8stail --namespace myapp [myapp-2136627869-k2l7b][rails] | I, [2017-09-11T11:20:27.327244 #1] INFO -- : [8437189b-7c93-46d1-b65c-8b3de10aabda] Started GET "/" for 127.0.0.1 at 2017-09-11 11:20:27 +0900 [myapp-2136627869-k2l7b][rails] | I, [2017-09-11T11:20:27.328345 #1] INFO -- : [8437189b-7c93-46d1-b65c-8b3de10aabda] Processing by PagesController#index as HTML [myapp-2136627869-k2l7b][rails] | I, [2017-09-11T11:20:27.329422 #1] INFO -- : [8437189b-7c93-46d1-b65c-8b3de10aabda] Rendering pages/index.html.erb within layouts/application [myapp-2136627869-k2l7b][rails] | I, [2017-09-11T11:20:27.331569 #1] INFO -- : [8437189b-7c93-46d1-b65c-8b3de10aabda] Rendered pages/index.html.erb within layouts/application (1.9ms) [myapp-2136627869-k2l7b][rails] | I, [2017-09-11T11:20:27.355639 #1] INFO -- : [8437189b-7c93-46d1-b65c-8b3de10aabda] Completed 200 OK in 27ms (Views: 26.3ms) [myapp-2136627869-k2l7b][nginx] | 172.17.0.1 - - [11/Sep/2017:02:20:27 +0000] "GET / HTTP/1.1" 200 635 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36" "-" [myapp-2136627869-k2l7b][nginx] | 172.17.0.1 - - [11/Sep/2017:02:20:27 +0000] "GET /assets/application-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.css HTTP/1.1" 200 0 "http://192.168.99.100:30552/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36" "-" [myapp-2136627869-k2l7b][nginx] | 172.17.0.1 - - [11/Sep/2017:02:20:27 +0000] "GET /packs/hello_react-bb75777111599a9f7979.js HTTP/1.1" 200 317132 "http://192.168.99.100:30552/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36" "-" [myapp-2136627869-nb49l][nginx] | 172.17.0.1 - - [11/Sep/2017:02:20:27 +0000] "GET /assets/application-73fbacba5f4d3758abe3a42ae957600060caa0a84677aed262b8ef009905a40f.js HTTP/1.1" 200 23226 "http://192.168.99.100:30552/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36" "-" [myapp-2136627869-k2l7b][nginx] | 172.17.0.1 - - [11/Sep/2017:02:20:28 +0000] "GET /favicon.ico HTTP/1.1" 200 0 "http://192.168.99.100:30552/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36" "-"
Secret
環境変数を指定します。以下の例では一点注意点があります。data:の中身はBase64という64種類の印字可能な英数字のみを用いた方式でエンコードしているだけのデータで、誰でもデコードして中身を閲覧することができます。そのためSecretのdata:の中身をGitのリポジトリに含めるべきではありません。今回はサンプルとしてわかりやすいように書いています。
apiVersion: v1 kind: Secret metadata: name: dotenv namespace: myapp type: Opaque data: DATABASE_URL: cG9zdGdyZXM6Ly9jb2RlOnppbmVAcG9zdGdyZXM6NTQzMi9teWFwcA== RACK_ENV: cHJvZHVjdGlvbg== RAILS_ENV: cHJvZHVjdGlvbg== SECRET_KEY_BASE: OTJhNDUzZDk5NjdjYmFjYjAzYzI2ZDNhNDdiY2ZkMmMxYzU0ZmIwZjZmMDViNTYwMmI5MTZlYzVlZTBiZTIzZTE2YjkwYjVkZjdmZGM3ZmM0OWM0ZGE5ZWY4MzVmYjEzNWRlYWQzODM5MmNlM2NmNmU1ZjZiNjNhMzkzYzQxMGE=
HorizontalPodAutoscaler
Podsのオートスケールを定義します。
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: myapp
namespace: myapp
spec:
maxReplicas: 20
minReplicas: 1
scaleTargetRef:
apiVersion: extensions/v1beta1
kind: Deployment
name: myapp
targetCPUUtilizationPercentage: 50 # CPU使用率を 50% を越えた段階で scale させる
Service
外からのトラフィックを定義します。
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: myapp
labels:
name: myapp
role: web
spec:
ports:
- port: 80 # 80 port にトラフィックを流す
name: http
protocol: TCP
selector:
name: myapp
role: web
type: LoadBalancer
Deployment
実際にアプリケーションをどう動かすか宣言します。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
name: myapp
role: web
name: myapp
namespace: myapp
spec:
minReadySeconds: 30
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 50% # replicas のPod数を超えて作成できる最大数(default 1) 50% なので、 2 Podまで作成できる
maxUnavailable: 0 # 更新処理中に使用できないPodの最大数(default 1) 0 にすることで、先に delete してリソースを開けて入れ替えではなく、 先に create してから入れ替えを行なうようにする。
replicas: 4 # template 以下に定義するPodの数を定義
template:
metadata:
labels:
name: myapp
role: web
name: myapp
spec:
volumes:
- name: nginx-conf # ConfigMap で作成した nginx.conf を mount する
configMap:
name: nginx-conf
- name: public # Pod 内で共有する volume mount
emptyDir: {}
- name: sockets # Pod 内で共有する volume mount
emptyDir: {}
containers:
- name: rails
image: quay.io/koudaiii/myapp:latest
imagePullPolicy: Always
ports:
- containerPort: 3000
readinessProbe: # traffic を受けるタイミングを指定
httpGet:
path: /ping # /ping で確認
port: 3000
initialDelaySeconds: 10 # 10s後にチェックする
timeoutSeconds: 1 # 1s 以上は timeout
lifecycle:
postStart: # application 起動後に静的ファイルを nginx 側で配信するように共有ボリュームにコピーする
exec:
command:
- "cp"
- "-r"
- "/app/public"
- "/nginx/"
resources:
limits:
memory: "256Mi" # 利用する memory のスペックを定義
cpu: "256m" # 利用する cpu のスペックを定義
env:
- name: "RAILS_SERVE_STATIC_FILES" # 静的ファイルを local から配信する
value: "enabled"
- name: "RAILS_LOG_TO_STDOUT" # log を stdout で出力する
value: "enabled"
envFrom:
- secretRef:
name: dotenv # Secret で作成した環境変数を利用する
command: ["bundle", "exec", "puma", "-C", "config/puma.rb"]
volumeMounts:
- mountPath: /nginx/public # 共有ボリュームに指定(静的ファイル)
name: public
- mountPath: /app/tmp/sockets # 共有ボリュームに指定(puma.sock)
name: sockets
- name: nginx
image: nginx:1.13.3-alpine
imagePullPolicy: Always
ports:
- containerPort: 80 # Deployment containerPort (Open Port)
lifecycle:
preStop: # nginx を graceful termitate する
exec:
# SIGTERM triggers a quick exit; gracefully terminate instead
command: ["/usr/sbin/nginx","-s","quit"]
volumeMounts:
- mountPath: /app/public # 共有ボリュームに保存されている静的ファイルを mount する
name: public
readOnly: true
- mountPath: /etc/nginx/ # ConfigMap で作成した nginx.conf を mount する
name: nginx-conf
readOnly: true
- mountPath: /app/tmp/sockets # 共有ボリュームに保存されてる puma.sock を mount する
name: sockets
maxSurgeとmaxUnavailableを指定することで、リリースされるまでのスケジューリングを制御することができます。
maxSurgeとmaxUnavailableを初期値で利用しreplicasを1にした場合、このままではmaxUnavailableの初期値が1のため、Podを消した(delete)のちに生成(create)してしまい、ダウンタイムが発生するので注意が必要です。
まとめて入れ替えるには、以下の通り設定する必要があります。用途としては、新旧のコードが混ざってはいけない場合や、オンメモリーでデータを固定したい場合(振り分けられる先でレスポンスの内容が変わってしまうのを避ける)に有効活用できます。
-
maxSurge: 100%⇒replicasの分追加で作成できる -
maxUnavailable: 0⇒使えない数を0にする
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 100% # maximum number of Pods that can be created above the desired number of Pods
maxUnavailable: 0 # maximum number of Pods that can be unavailable during the update process.
CronJob
サービスを運用していくと、定期的にJobを実行する必要が出てきます。その場合はCronJobを利用します。CronJobはまだアルファ版ですが、先日v1beta1に昇格するPull Requestがマージされたため、Kubernetesの1.8系または1.9系からはDeploymentと同様にベータ版として利用できそうです。
apiVersion: batch/v2alpha1
kind: CronJob
metadata:
namespace: myapp
name: myapp-job
labels:
name: myapp-job
role: job
spec:
schedule: "*/15 * * * *"
concurrencyPolicy: "Replace" # 前回のJob が実行中だった場合には入れ替えて再度実行する
successfulJobsHistoryLimit: 10 # 10 回分のログとして保持
failedJobsHistoryLimit: 5 # 5 回分のログとして保持
suspend: false
jobTemplate:
metadata:
name: myapp-job
labels:
job: myapp-job
role: job
spec:
template:
metadata:
name: myapp-job
labels:
job: myapp-job
role: job
spec:
hostname: scheduler
containers:
- name: job
image: quay.io/koudaiii/myapp:latest
imagePullPolicy: Always
command: # Job の retry を許容しない場合に、bash コマンドとして実行して、必ず一回で終わるようできる
- "/bin/bash"
- "-l"
- "-c"
- "bundle exec rails hello || true"
env:
- name: "RAILS_SERVE_STATIC_FILES"
value: "enabled"
- name: "RAILS_LOG_TO_STDOUT"
value: "enabled"
envFrom:
- secretRef:
name: dotenv
restartPolicy: Never
$ kubectl create -f kubernetes/jobs/myapp-job.yaml cronjob "myapp-job" created $ kubectl get cronjob -n myapp NAME SCHEDULE SUSPEND ACTIVE LAST-SCHEDULE myapp-job */15 * * * * False 0 <none> $ kubectl get job -n myapp NAME DESIRED SUCCESSFUL AGE db-migrate-koudaiii-2017091116251505114716 1 1 1h myapp-job-1505118600 1 1 22m $ kubectl logs myapp-job-1505118600-sr7wn -n myapp Hello, World!
