KubernetesのLabel / NodeSelector / Annotationの使い方

## 概要
Kubernetesのmanifestファイルの書き方について説明する。
このページでは、以下の3つについて説明する。

- [Labelの使い方](#labelの使い方)
- [NodeSelectorの使い方](#nodeselectorの使い方)
- [Annotationの使い方](#annotationの使い方)

## 目次
- [Kubernetesハンズオン目次](https://qiita.com/Esfahan/items/4fcf51622fed20dffb88)

## Labelの使い方
Labelは他のリソースから参照するときに利用する。
以下の例では、ServiceからDeploymentへリクエストを流すために各種labelを合わせている。

```yaml:deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      # 後述のspec.template.metadata.labels.appと合わせる
      app: sample-app
  template:
    metadata:
      labels:
        # 上記のspec.selector.matchLabels.appと合わせる
        app: sample-app
    spec:
      containers:
        # Dockerコンテナ名
        - name: http-container
          # 利用するDockerイメージ
          image: php:7.0-apache
          # localイメージ利用。ただし、localに指定したイメージが存在しない場合はレポジトリを参照する
          imagePullPolicy: IfNotPresent
          ports:
            # service.ymlで定義する外部公開用port設定の箇所と名前を合わせることでbindされる
            # nameは15文字以内でないとエラーになる
            - name: ha-inner-port
              # コンテナ内のport
              containerPort: 80
```

```yaml:service.yml
apiVersion: v1
kind: Service
metadata:
  name: sample-service
spec:
  selector:
    # deployment.ymlのspec.selector.matchLabels.appと合わせる
    app: sample-app
  ports:
    - name: ha-outer-port
      # 外部公開するport
      port: 8080
      # deployment.ymlで定義したport名と合わせることでbindされる
      targetPort: ha-inner-port
  type: NodePort
```

### apply

```shell-session
$ sudo kubectl apply -f pod.yml
pod/nginx created
```

### LABELを確認

```shell-session
$ sudo kubectl get all --show-labels
NAME                                     READY   STATUS    RESTARTS   AGE   LABELS
pod/sample-deployment-85f4cb9d7c-2qncx   1/1     Running   0          20s   app=sample-app,pod-template-hash=85f4cb9d7c


NAME                     TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE    LABELS
service/kubernetes       ClusterIP   10.96.0.1     <none>        443/TCP          111m   component=apiserver,provider=kubernetes
service/sample-service   NodePort    10.98.7.194   <none>        8080:32661/TCP   20s    app=http-service


NAME                                READY   UP-TO-DATE   AVAILABLE   AGE   LABELS
deployment.apps/sample-deployment   1/1     1            1           20s   <none>

NAME                                           DESIRED   CURRENT   READY   AGE   LABELS
replicaset.apps/sample-deployment-85f4cb9d7c   1         1         1       20s   app=sample-app,pod-template-hash=85f4cb9d7c
```

### 動作確認
WEBページが閲覧できれば、ServiceからDeploymentへのリクエストが流れたことになる。

```shell-session
$ curl $(sudo minikube service sample-service --url)
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access /
on this server.<br />
</p>
<hr>
<address>Apache/2.4.25 (Debian) Server at 172.21.115.122 Port 32691</address>
</body></html>
```


## LabelSelectorの使い方
LabelSelectorには以下の2種類がある。

- matchLabels
- matchExpressions

### matchLabels
指定したLabelと全て一致したものが対象になる。

```yaml
matchLabels:
  app: nginx
  tier: frontend
```

### matchExpressions
matchExpressionsでは、`operator`を使い、柔軟な条件を設定することができる。
`operator`には以下を指定することができる。

- In
- NotIn
- Exists
- DoesNotExists

具体的な書き方は以下の通り。

```yaml
matchExpressions:
# envがprodかstgのいずれかである場合
- key: env
  operator: In
  values: [prod, stg]
# appがprodかstgのいずれかでもない場合
- key: app
  operator: NotIn
  values: [sample001, sample002]
# appが存在する場合
- key: app
  operator: Exists
# tierが存在する場合
- key: tier
  operator: DoesNotExist
```

### 例
envがprodもしくはstgの場合という条件を設定する。

```yaml
matchExpressions:
- key: env
  operator: In
  values: [prod, stg]
```

この条件でマッチするもの、しないものは以下のとおりである。

マッチする

```yaml
labels:
  app: sample-app
  tier: frontend
  env: prod
```

マッチする

```yaml
labels:
  app: sample-app
  tier: frontend
  env: stg
```

マッチしない

```yaml
labels:
  app: sample-app
  tier: frontend
  env: dev
```

参考：[Kubernetes道場 8日目 - ReplicaSet / Deploymentについて](https://cstoku.dev/posts/2018/k8sdojo-08/#label-selector)

## NodeSelectorの使い方
NodeSelectorとは、Podを特定のNodeにスケジューリングする仕組みである。
ここでもLabelSelectorを使うが、先述のものとは違い、matchExpressionsは使えず、完全一致のみとなる。

例として、`environment: dev`というLabelを持ったNodeにPodをスケジューリングさせてみる。
まずは、存在しないLabelを指定しスケジューリングされないことを確認し、その後に、存在するLabelを指定してスケジューリングさせるという手順で行う。

### Podを定義
`environment: dev`のNodeにスケジューリングする設定にする。

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: node-selector
spec:
  containers:
  - name: nginx
    image: nginx
  # 以下でNodeの指定をする
  nodeSelector:
    environment: dev
  terminationGracePeriodSeconds: 0
```

### ラベルを確認
しかし、`environment: dev`は存在していない。

```shell-session
$ sudo kubectl get node --show-labels
NAME       STATUS   ROLES    AGE     VERSION   LABELS
minikube   Ready    master   3h35m   v1.14.3   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=minikube,kubernetes.io/os=linux,node-role.kubernetes.io/master=
```

### スケジューリング失敗
存在しないNodeのLabelを指定した場合は、エラーとなりスケジューリングされない。

```shell-session
$ sudo kubectl describe pod/node-selector
# **snip**
Events:
  Type     Reason            Age                From               Message
  ----     ------            ----               ----               -------
  Warning  FailedScheduling  24s (x2 over 24s)  default-scheduler  0/1 nodes are available: 1 node(s) didn't match node selector.
```

### NodeにLabelを貼る。
以下のコマンドでNodeに`environment: dev`というLabelを貼る。

```shell-session
$ sudo kubectl label node/minikube environment=dev
```

`environment=dev`がLabelに追加された。

```shell-session
$ sudo kubectl get node --show-labels
NAME       STATUS   ROLES    AGE     VERSION   LABELS
minikube   Ready    master   3h41m   v1.14.3   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,environment=dev,kubernetes.io/arch=amd64,kubernetes.io/hostname=minikube,kubernetes.io/os=linux,node-role.kubernetes.io/master=
```

### スケジューリングされたことを確認
これでPodがNodeにスケジューリングされた。

```shell-session
$ sudo kubectl describe pod/node-selector
# **snip**
Events:
  Type     Reason            Age                    From               Message
  ----     ------            ----                   ----               -------
  Warning  FailedScheduling  2m28s (x5 over 6m30s)  default-scheduler  0/1 nodes are available: 1 node(s) didn't match node selector.
  Normal   Scheduled         101s                   default-scheduler  Successfully assigned default/node-selector to minikube
  Normal   Pulled            100s                   kubelet, minikube  Container image "nginx:alpine" already present on machine
  Normal   Created           100s                   kubelet, minikube  Created container nginx
  Normal   Started           100s                   kubelet, minikube  Started container nginx

$ sudo kubectl get pod
NAME            READY   STATUS    RESTARTS   AGE
node-selector   1/1     Running   0          7m16s
```

## Annotationの使い方
Labelと同じようなことができるが、Labelと違い、metadataなどをもたせるのが一般的らしい。

https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
> You can use Kubernetes annotations to attach arbitrary non-identifying metadata to objects. Clients such as tools and libraries can retrieve this metadata.

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: annotations-demo
  annotations:
    imageregistry: "https://hub.docker.com/"
spec:
  containers:
  - name: nginx
    image: nginx:1.7.9
    ports:
    - containerPort: 80
```

## 関連記事
- [【KubernetesのAffinity】その１ - Node Affinityの使い方](https://qiita.com/Esfahan/items/aeddf89428441ec3437c)
- [【KubernetesのAffinity】その２ - Pod Affinity / Pod Anti-Affinityの使い方](https://qiita.com/Esfahan/items/a673317a29ca407e5ae7)


## 参考
- [Kubernetes道場 17日目 - Label / NodeSelector / Annotationについて](https://cstoku.dev/posts/2018/k8sdojo-17/)
- [KubernetesのLabelとAnnotationの使い分けについて](https://tomokazu-kozuma.com/on-the-distinction-between-label-and-annotation-of-kubernetes/)