この記事は前回の記事と紐づいています。適宜参照してください。
kubectlコマンド
まずはPodを作成しましょう。
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
/
/
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
コマンドを利用してデプロイしてください。
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に対してリクエストを送ってみましょう。
/
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
/
(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
/
HTTP/1.1 200 OK
Server: nginx/1.16.1
...
<h1>Welcome to nginx!</h1>
...
NodePort
まずはtype: NodePortのServiceを以下のマニュフェストファイルから作成しましょう。
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を作成してください。
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週間悩みました。無知)
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"]
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
$ 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
コマンドでデプロイしてみましょう。
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
/
/
Hello World
/
実際に環境変数が入っていることが確認できました。
Secret
Secretはまず自分で入れたい環境変数をbase64でエンコードします。
$ echo -n 'Hello World!' | base64
SGVsbG8gV29ybGQh
それをSecretに入れます。
apiVersion: v1
kind: Secret
metadata:
name: secret
data:
key: SGVsbG8gV29ybGQh
これで上のConfigMapと同じ要領でPodをデプロイすれば環境変数key
にHello World!が設定されます。
すいません。この項目については私自身きちんと理解しきれていないので今回は控えさせていただきます。
今回はここまでです。
一応直近3つの記事で自分が理解したことは書けたかなと思います。
それでは