この記事は前回の記事と紐づいています。適宜参照してください。
Kubernetes基礎とアーキテクチャ
kubectlコマンド
まずはPodを作成しましょう。
# basic-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:stable-alpine
---
apiVersion: v1
kind: Pod
metadata:
name: nginx-no-label
spec:
containers:
- name: nginx
image: nginx:stable-alpine
マニフェストファイルに記載されたリソースを作成する時は、kubectl apply
コマンドを使用します。
$ kubectl apply -f basic-pod.yaml
pod/nginx created
pod/nginx-no-label created
起動中のリソースを一覧表示するときはkubectl get
コマンドを使います。Podを一覧するときはkubectl get pods
です。
$ kubectl get pods NAME READY STATUS RESTARTS AGE nginx 1/1 Running 0 2m44s nginx-no-label 1/1 Running 0 2m17s
より詳しい情報を見るときは-o wide
を付けます。
$ kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx 0/1 ContainerCreating 0 3s <none> gke-kubernetes-1-default-pool-2c12379a-jkjk <none> <none> nginx-no-label 0/1 ContainerCreating 0 3s <none> gke-kubernetes-1-default-pool-2c12379a-jkjk <none> <none>
Podにはラベルを付けることができます。特定のラベルがついているPodの一覧は-l
オプションを使用することでラベルを指定することができます。
$ kubectl get pods -l app=web NAME READY STATUS RESTARTS AGE nginx 1/1 Running 0 22m
起動中のPodの中にあるコンテナにログインすることは非常に多いので覚えましょう。kubectl exec
コマンドでログインができます。
$ kubectl exec -it nginx ash / # / # cat /etc/hosts # Kubernetes-managed hosts file. 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet fe00::0 ip6-mcastprefix fe00::1 ip6-allnodes fe00::2 ip6-allrouters 10.16.2.2 nginx / #
Nginxサーバのコンテナを起動するPodに直接トラフィックを送ってみましょう。kubectl port-foward
でPodへのポートフォワードを行うことができ、kubectl port-foward <Pod名> ローカルポート:Podのポート番号
のように使います。
$ kubectl port-forward nginx 8081:80 Forwarding from 127.0.0.1:8081 -> 80
起動中のPodの定義を少しだけ変更したい場合、わざわざマニフェストファイルを書き出してkubectl apply
をするのは面倒です。そんな時はkubectl edit <Pod名>
で定義を直接変更することができます。
$ kubectl edit pods nginx ※vimが起動して編集することができます
k8sのリソースを一覧表示するコマンドは以下です。やってみてください。
$ kubectl api-resources (省略)
それぞれのリソースにどんな定義があるかはkubectl explain
コマンドで調べられます。
$ kubectl explain Pod.spec KIND: Pod VERSION: v1 RESOURCE: spec <Object> DESCRIPTION: Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status PodSpec is a description of a pod. FIELDS: ...
Podをネットワークに公開するService
ClusterIP
以下のマニフェストファイルには先ほど作ったapp=web
のラベルを持つnginxのPodに対応したselectorを持つClusterIP Serviceを起動する定義が書いてあります。kubectl
コマンドを利用してデプロイしてください。
# nginx-clusterip.yaml apiVersion: v1 kind: Service metadata: name: nginx-clusterip spec: selector: app: web type: ClusterIP ports: - port: 80 targetPort: 80
ClusterIPはクラスタの内部に公開するServiceなので、このままローカル環境にいてはトラフィックを送れません。そのため、alpineコンテナを起動するPodをデバック用のPodとして用意します。このようなケースでわざわざマニフェストファイルを作るのは面倒なので、kubectl run
コマンドで直接Podを作成しましょう。
$ kubectl run -it --generator=run-pod/v1 alpine --image=alpine ash If you don't see a command prompt, try pressing enter. / # / #
プロンプトが表示されたら、apkパッケージのアップデート、curlコマンドのインストールを行いましょう。インストールが終わったら、nginx-clusteripに対してリクエストを送ってみましょう。
/ # apk update fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/main/x86_64/APKINDEX.tar.gz fetch http://dl-cdn.alpinelinux.org/alpine/v3.11/community/x86_64/APKINDEX.tar.gz v3.11.5-40-ge30587e6d0 [http://dl-cdn.alpinelinux.org/alpine/v3.11/main] v3.11.5-39-g0b5b5381b3 [http://dl-cdn.alpinelinux.org/alpine/v3.11/community] OK: 11268 distinct packages available / # apk add curl (1/4) Installing ca-certificates (20191127-r1) (2/4) Installing nghttp2-libs (1.40.0-r0) (3/4) Installing libcurl (7.67.0-r0) (4/4) Installing curl (7.67.0-r0) Executing busybox-1.31.1-r9.trigger Executing ca-certificates-20191127-r1.trigger OK: 7 MiB in 18 packages / # curl -i nginx-clusterip HTTP/1.1 200 OK Server: nginx/1.16.1 ... <h1>Welcome to nginx!</h1> ...
NodePort
まずはtype: NodePortのServiceを以下のマニュフェストファイルから作成しましょう。
# nginx-nodeport.yaml apiVersion: v1 kind: Service metadata: name: nginx-nodeport spec: selector: app: web type: NodePort ports: - port: 80 targetPort: 80 nodePort: 30001
NodePortにトラフィックを送信するためにはもちろんNodeのIPアドレスが必要です。k8sクラスタを構成するNodeのIPアドレスはkubectl get nodes
で調べられます。
$ kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME gke-kubernetes-1-default-pool-0c238d8b-p9z9 Ready <none> 28h v1.14.10-gke.27 10.146.0.52 34.85.11.21 Container-Optimized OS from Google 4.14.138+ docker://18.9.7 gke-kubernetes-1-default-pool-2c12379a-jkjk Ready <none> 73m v1.14.10-gke.27 10.146.0.54 35.221.123.134 Container-Optimized OS from Google 4.14.138+ docker://18.9.7 gke-kubernetes-1-default-pool-c724d8c0-l09b Ready <none> 7d8h v1.14.10-gke.27 10.146.0.53 104.198.92.230 Container-Optimized OS from Google 4.14.138+ docker://18.9.7
この出力の例では、34.85.11.21
、35.221.123.134
または104.198.92.230
です。早速、34.85.11.21
の30001番にトラフィックを送ってみたいところですが、GCPではファイアウォールルールが許可されていないため、トラフィックを送信することができません。まずはこのGKEクラスタが作成されているdefaultネットワークに対して、NodePortの範囲である30000~32767
番のポートへのTCPを許可するルールを作ります。
$ gcloud compute firewall-rules create allow-nodeport \ --allow=tcp:30000-32767 \ --source-ranges=0.0.0.0/0 \ --network=default Creating firewall...⠧Created [https://www.googleapis.com/compute/v1/projects/ca-container-book/global/firewalls/allow-nodeport]. Creating firewall...done. NAME NETWORK DIRECTION PRIORITY ALLOW DENY DISABLED allow-nodeport default INGRESS 1000 tcp:30000-32767 False
こうすることでNodePortへのトラフィックが許可されます。
$ curl -i 34.85.11.21:30001 HTTP/1.1 200 OK Server: nginx/1.16.1 ...
LoadBalancer
実際にコンテナを外部に公開する際には、LoadBalancerを使います。以下のマニフェストファイルをkubectl
でデプロイして、LoadBalancerを作成してください。
#nginx-loadbalancer.yaml apiVersion: v1 kind: Service metadata: name: nginx-lb spec: selector: app: web type: LoadBalancer ports: - port: 80 targetPort: 80
ここで、EXTERNAL-IPが”pending”となっているはずです。これは、GCPのロードバランサが外部IPアドレスを確保している最中であることを意味しています。ロードバランサが外部IPアドレスの確保に成功すると、"pending"が次のように置き換わります。
$ kubectl get svc nginx-lb -w NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-lb LoadBalancer 10.0.12.136 <pending> 80:30097/TCP 5s nginx-lb LoadBalancer 10.0.12.136 35.243.113.150 80:30097/TCP 73s
kubectl get
コマンドの最後につけている-w
は、一覧表示中のリソースに変更があった場合に自動的に新しい情報を出力してくれるオプションです。この場合は、curlを使って次のようにロードバランサにトラフィックを送信できます。
$ curl -i 35.243.113.150 HTTP/1.1 200 OK Server: nginx/1.16.1
試しに上記のIPに別のWindowからアクセスしてみましょう。
"http://35.243.113.150"
どうですか?Welcome to nginxと出てきましたか?
コンテナの監視
本番環境でコンテナを起動し続けるためには、readinessProbeとlivenessProbeの設定が不可欠です。ここでは2つのProbeの動作を確認するためのコンテナを作ってみます。
まずはそのコンテナをビルドします。以下のDockerfile, main.goを使ってください。
そのあと、GCR(Google Containar Registry)にpushします。(DockerhubではなくGoogleの自分のローカルGCP上に安全に保存することができます)
YOUR_PROJECT_ID
の部分は、自分のGCPプロジェクトIDに変更して実行してください。(私はここをプロジェクト名にしててpushできなくて1週間悩みました。無知)
# Dockerfile FROM golang:latest as builder WORKDIR /app COPY main.go ./ RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . FROM alpine:latest RUN apk --no-cache add ca-certificates curl WORKDIR /root/ COPY --from=builder /app/main . EXPOSE 8080 80 CMD ["./main"]
// main.go package main import ( "fmt" "log" "net/http" "os" ) var isHealth bool var hostname = os.Getenv("HOSTNAME") func main() { if os.Getenv("HEALTHY") == "FALSE" || os.Getenv("HEALTHY") == "false" { isHealth = false } else { isHealth = true } http.HandleFunc("/health", healthyHandler) http.HandleFunc("/unhealth", unhealthyHandler) http.HandleFunc("/ping", pingHandler) http.HandleFunc("/", indexHandler) port := os.Getenv("PORT") if port == "" { port = "80" log.Printf("HostName is %s", hostname) } log.Printf("Listening on port %s", port) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil)) } func healthyHandler(w http.ResponseWriter, r *http.Request) { isHealth = true _, err := fmt.Fprintf(w, "%s: change response code to 200", hostname) if err != nil { w.WriteHeader(http.StatusOK) } } func unhealthyHandler(w http.ResponseWriter, r *http.Request) { isHealth = false _, err := fmt.Fprintf(w, "%s: change response code to 503", hostname) if err != nil { w.WriteHeader(http.StatusOK) } } func indexHandler(w http.ResponseWriter, r *http.Request) { if isHealth == true { _, err := fmt.Fprintf(w, "HostName is %s", hostname) if err != nil { w.WriteHeader(http.StatusOK) } return } w.WriteHeader(http.StatusServiceUnavailable) return } func pingHandler(w http.ResponseWriter, r *http.Request) { _, err := fmt.Fprintf(w, "%s: pong", hostname) if err != nil { w.WriteHeader(http.StatusOK) } }
$ docker build -t gcr.io/YOUR_PROJECT_ID/probe .
GCRにコンテナをpushするために、dockerコマンドに対してGCPに認証させる必要があります。
$ gcloud auth configure-docker
ビルドしたイメージをpushします。
$ docker push gcr.io/YOUR_PRIOJECT_ID/probe
GCP GUIにContainar Registryにコンテナがpushされているのを確認できると思います。
コンテナイメージがpushできたら、さっそくk8sにデプロイします。以下のマニフェストファイルをkubectl
コマンドでデプロイしてください。
apiVersion: v1 kind: Pod metadata: name: probe labels: app: probe spec: containers: - name: probe image: gcr.io/k8s-seminar-272301/probe readinessProbe: initialDelaySeconds: 10 periodSeconds: 30 failureThreshold: 2 successThreshold: 1 timeoutSeconds: 5 httpGet: port: 80 path: / livenessProbe: httpGet: port: 80 path: / --- apiVersion: v1 kind: Service metadata: name: probe spec: selector: app: probe type: NodePort ports: - port: 80 targetPort: 80 nodePort: 30003
readinessProbeに成功している場合、kubectl get pods
のREADYステータスが1/1となります。(この数字は正常に動いているコンテナ数/内部コンテナ数
)
このコンテナはルートパス/
へのリクエストの結果が/health
へのリクエストで成功するようになり、/unhealth
へのリクエストで失敗するようになります。つまり、この状態で/unhealth
へリクエストを送ると、readinessProbeに失敗するようになります。
# 最初はリクエストに成功する $ curl -i 34.84.230.195:30003 HTTP/1.1 200 OK #/unhealthにリクエストを送る $ curl -i 34.84.230.195:30003/unhealth HTTP/1.1 200 OK #リクエストに失敗するようになる $ curl -i 34.84.230.195:30003 HTTP/1.1 503 Service Unavailable
この状態でREADYステータスを確認します。
$ kubectl get po probe -w NAME READY STATUS RESTARTS AGE probe 0/1 Running 3 42m probe 1/1 Running 3 42m
readinessProbeはREADYではないPodにトラフィックを送信しないように振る舞います。
livenessProbeはREADYではないPodを再起動します。
いまはlivenessProbeがいるのですぐに再起動してしまいますが、livenessProbeをコメントアウトしてPodを作り直すと、503
のあとにしばらくしてリクエストを送ると何も帰ってこないというreadinessProbeの振る舞いを確認することができます。
ConfigMap,Secret
ConfigMap
以下のマニフェストファイルはspec.envFrom
でConfigMapを指定し、指定しているConfigMapを作成するマニフェストファイルです。
では、実際にkubectl
コマンドでデプロイしてみましょう。
# pod-with-configmap.yaml apiVersion: v1 kind: Pod metadata: name: nginx-with-config labels: app: web spec: containers: - name: nginx image: nginx:stable-alpine envFrom: - configMapRef: name: env-value --- apiVersion: v1 kind: ConfigMap metadata: name: env-value data: ENV: "Hello World"
作成したPodにログインして環境変数を確認してみましょう。
$ kubectl exec -it nginx-with-config ash / # / # echo $ENV Hello World / #
実際に環境変数が入っていることが確認できました。
Secret
Secretはまず自分で入れたい環境変数をbase64でエンコードします。
$ echo -n 'Hello World!' | base64 SGVsbG8gV29ybGQh
それをSecretに入れます。
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: secret
data:
key: SGVsbG8gV29ybGQh
これで上のConfigMapと同じ要領でPodをデプロイすれば環境変数key
にHello World!が設定されます。
Kubernetesにおけるコンテナのスケール
すいません。この項目については私自身きちんと理解しきれていないので今回は控えさせていただきます。
今回はここまでです。
一応直近3つの記事で自分が理解したことは書けたかなと思います。
それでは