kubectl run/create/expose のススメ

kubectlにはrun/cretae/exposeコマンドを利用することで、様々なリソースをYAMLを書かずに作成することができます。
私は次の２点で、kubectl run/create/exposeが素敵だなと思ったので調べた内容を整理します。
・試験時に高速にYAMLを用意してドキュメントを見る回数を減らせる
・勉強時に検証のメインでない部分に時間をかけずに済む

# kubectl run
`kubectl run XXX`はpodやdeployのリソースをワンライナーで作成することができます。
例えば、image=nginx:alpineを利用したpodを作成する場合は下記のようにします。

```sh
$ kubectl run --restart=Never nginx --image=nginx:alpine 
pod/nginx created

$ kubectl get pod
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          72s
```

ここまでは多くのドキュメントに書かれていると思うのですが、他にも多くのオプションがあり、それらを有効利用することで効率的なkubectl runライフを送れます。
主要（偏見）なオプションを見てみましょう。
ちなみに[Kubectl Reference Docs](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#run)や、`kubectl run -h`でも確認できます。

| Option | Sample | Description |
|-----------|------------|------------|
| --labels=''<br>-l | --labels=bu=finance,env=dev<br>-l bu=finance,env=dev | 作成するリソースに付与するラベルを設定します。複数設定する場合は`,`を利用して繋げます。`metadata.labels`に該当する設定です。|
| --namespace=''<br>-n | --namespace=my-ns<br>-n my-ns | 該当リソースをデプロイするnamespaceを指定します。`metadata.namespaces.`に該当する設定です。|
| --restart='' | --restart=Never | Always, OnFailure, Neverの3種類があります。Alwaysを選択(Default)するとDeploymentリソースが、OnFailureを選択するとJobリソースが、Neverを選択するとPodリソースが作成できます。`pod.sepc.restartPolicy`に該当する設定です。個人的には[kubectl Usage Conventions](https://kubernetes.io/docs/reference/kubectl/conventions/)を一読することをお勧めします。|
| --serviceaccount='' | --serviceaccount=my-sa | Service accountを設定します。`pod.sepc.serviceAccountName`に該当する設定です。|
| --image='' | --image=nginx:alpine | 指定したimageを利用したcontainerをpodに作成します。`pod.spec.containers.image`に該当する設定です。|
| --env=[] | --env=HOSTNAME=local | Containerで利用する環境変数を`NAME=VALUE`の形で設定します。ConfigMapやSecretsリソースは使用しません。`pod.spec.containers.env`に該当する設定です。|
| --port='' | --port=80 | Containerが外部に公開するport番号を指定します。`pod.spec.containers.ports.containerPort`に該当する設定です。|
| --replicas=''<br>-r | --replicas=2<br>-r 2 | Replica数を指定します。デフォルトでは1が指定されています。`deployment.spec.replicas`に該当する設定です。 |
| --command | --command -- sleep 4800 | Container起動時に実行するコマンドを設定します。Kubernetetsでいう`command`にあたり、Dockerfileでいう、`ENTRYPOINT`です。 `pod.spec.containers.command.`に該当する設定です。|
| --  | -- sleep 4800 | Container起動時に引数として渡します。Kubernetetsでいう`args`にあたり、Dockerfileでいう、`CMD`です。 `pod.spec.containers.args.`に該当する設定です。*(2019/10/22追記)*|
| --requests='' | --requests='cpu=0.1,memory=10Mi' | Containerのリソース制限を設定できます。`pod.spec.containers.resources.limits.`に該当する設定です|
| --limits='' | --limits='cpu=0.2,memory=20Mi' | Containerのリソース制限を設定できます。`pod.spec.containers.resources.limits.`に該当する設定です。|
| --schedule='' | --schedule='*/10 * * * *' | 指定したScheduleによるCronJobを作成します。時間設定はcronのルールに乗っとります。`cronjob.spec.schedule`に該当する設定です。|
| --dry-run | --dry-ryn -o yaml | リソースのデプロイをせずに実行可能かチェックができます。 |
| --rm | --rm | 実行後に、コマンドで対象としたリソースを削除します。-itと併用して利用することでトラブルシュートに楽です。 |

### Podを作成する(--restart=Never)
例えば、なにかしらのアプリケーションをテストするためにpostgres DBを用意したい時にYAMLから書いていたら面倒です。
下記のような要件とした場合、ワンライナーで作成できます。
・name: my-db
・image: postgres:12-alpine
・env: POSTGRES_PASSWORD=example
・port： 5432
・resources: requests=`cpu=0.1,memory=100Mi` limits=`cpu=0.2,memory=100Mi`

```sh
$ kubectl run my-db --restart=Never --image=postgres:12-alpine --port=5432 --env=POSTGRES_PASSWORD=example --namespace=my-ns --requests=`cpu=0.1,memory=50Mi` --limits=`cpu=0.2,memory=100Mi` --dry-run -o yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: my-db
  name: my-db
spec:
  containers:
  - env:
    - name: POSTGRES_PASSWORD
      value: example
    image: postgres:12-alpine
    name: my-db
    ports:
    - containerPort: 5432
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Never
status: {}
```

また個人的に好きなのは`--rm`オプションです。
例えばトラブルシュートで疎通性を確認する際に、nslookupやcurlなどを利用したい場合があるかと思います。
その際になにかしらPodを作成するのはいいのですが、テストしたあと削除を忘れたりなど、汚れがちです。
このオプションはそのリソースが完了すると、自動的に削除されるオプションです。

例えばServiceリソースのmy-lbをnslookupしたい時、このような工程が必要です。

```sh
$ kubectl run ns --restart=Never --image=busybox:1.28 --command -- nslookup my-lb
pod/ns created

$ kubectl get pod
NAME                        READY   STATUS      RESTARTS   AGE
my-nginx-6c79cbc966-5kn76   1/1     Running     0          26m
my-nginx-6c79cbc966-jr74c   1/1     Running     0          26m
ns                          0/1     Completed   0          5s

$ kubectl logs ns
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      my-lb
Address 1: 10.96.155.99 my-lb.default.svc.cluster.local

$ kubectl delete pod ns
pod "ns" deleted
```

これを`--rm`オプションを利用することで、簡単になります。標準出力のために、`-it`オプションも必要です。

```sh
$ kubectl run ns --restart=Never --image=busybox:1.28 --rm -it --command -- nslookup my-lb
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      my-lb
Address 1: 10.96.155.99 my-lb.default.svc.cluster.local
pod "ns" deleted

$ kubectl get pod
NAME                        READY   STATUS    RESTARTS   AGE
my-nginx-6c79cbc966-5kn76   1/1     Running   0          29m
my-nginx-6c79cbc966-jr74c   1/1     Running   0          29m
```


*(2019/10/22追記)*
今まで`--command`による`ENTRYPOINT/ commands`しか作成できないと思っていましたが、`CMD / args`も作成できると知ったので追記します。
`--`は何気なく書いていますが、これは引数として渡す内容を以降に記載するのですが、これを`--command`を書かずに記載します。つまりただの引数として渡すだけなので`CMD / args`になります。
考えればそりゃそうか、、ですが面白かったので共有です。
(ちなみに、引数しか渡していないのでpodの作成は失敗します。`pod.spec.containers.command`で`ENTORYPOINT`として`sleep`を渡してあげれば動きます。)

```
$ kubectl run --restart=Never busybox --image=busybox --dry-run -o yaml -- 4800
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: busybox
  name: busybox
spec:
  containers:
  - args:
    - "4800"
    image: busybox
    imagePullPolicy: IfNotPresent
    name: busybox
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Never
status: {}
```


### Deploymentを作成する(--restart=Always)
Deploymentリソースを作成したい場合は次のように作ります。
--restart=Alwaysはデフォルト値なので、記載しなくてもDeploymentリソースとして作成されます。

```sh
$ kubectl run my-nginx --restart=Always --image=nginx:alpine --replicas=2 --dry-run -o yaml
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    run: my-nginx
  name: my-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      run: my-nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: my-nginx
    spec:
      containers:
      - image: nginx:alpine
        name: my-nginx
        resources: {}
status: {}
```

### Jobを作成する(--restart=OnFailure)
例えば公式ドキュメントの[Jobs](https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/)をサンプルに、perlを利用して円周率3.14を出力するJobはこのようにワンライナーで作成できます。

```sh
$ kubectl run pi --restart=OnFailure --image=perl --dry-run -o yaml --command -- perl -Mbignum=bpi -wle "print bpi(2000)"
kubectl run --generator=job/v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
apiVersion: batch/v1
kind: Job
metadata:
  creationTimestamp: null
  labels:
    run: pi
  name: pi
spec:
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: pi
    spec:
      containers:
      - command:
        - perl
        - -Mbignum=bpi
        - -wle
        - print bpi(2000)
        image: perl
        name: pi
        resources: {}
      restartPolicy: OnFailure
status: {}

$ k logs pi-xwjq9
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901
```

### CronJobを作成する(--restart=OnFailure)
先ほど作成したJobをCronJobで作成する例が下記です。CronJobリソース自体は`--schedule=''`を利用して作成することができますが、デフォルトでは`restart=Always`となるため作成できません。そのため`--restart=Never or OnFailure`の設定が必要です。

```sh
$ kubectl run pi --image=perl --restart=Never --schedule="*/10 * * * *" --dry-run -o yaml --command -- perl -Mbignum=bpi -wle "print bpi(2000)"
kubectl run --generator=cronjob/v1beta1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  creationTimestamp: null
  labels:
    run: pi
  name: pi
spec:
  concurrencyPolicy: Allow
  jobTemplate:
    metadata:
      creationTimestamp: null
    spec:
      template:
        metadata:
          creationTimestamp: null
          labels:
            run: pi
        spec:
          containers:
          - command:
            - perl
            - -Mbignum=bpi
            - -wle
            - print bpi(2000)
            image: perl
            name: pi
            resources: {}
          restartPolicy: Never
  schedule: '*/10 * * * *'
status: {}

$ kubectl get event | grep cronjob/pi
15m         Normal    SuccessfulCreate   cronjob/pi                Created job pi-1569048000
14m         Normal    SawCompletedJob    cronjob/pi                Saw completed job: pi-1569048000, status: Complete
5m12s       Normal    SuccessfulCreate   cronjob/pi                Created job pi-1569048600
4m51s       Normal    SawCompletedJob    cronjob/pi                Saw completed job: pi-1569048600, status: Complete
```

# kubectl expose
`kubectl expose XXX`はserviceリソースをワンライナーで作成することができます。
exposeは晒すという意味なので、PODと外部をアクセスするためのリソースと考えるとわかりやすいですね。

主要なオプションを以下にまとめます。

| Option | Sample | Description |
|-----------|------------|------------|
| --port='' | --port=80 | Serviceリソースが晒す(expose)するPort番号です。外部からはこのPort番号にアクセスするとServiceリソースが紐づいたリソースにロードバランスされます。|
| --target-port='' | --target-port=80 | ServiceリソースにひもづくContainerが公開しているPort番号を指定します。指定しないと`--port`で指定した番号を引用します。|
| --type='' | --type=NodePort | 作成するServiceリソースのtypeを指定します。ClusterIP(Default), NodePort, LoadBalancer, ExternalNameが指定できます。|
| --name='' | --name=my-svc | 作成するServiceリソースの名前を指定します。指定しないとexpose対象としたリソースの名前を使用します。|

`--selector=''`オプションを利用することで、Serviceリソースがひもづくリソースを指定できますが、exposeで指定したリソースを自動で参照するため個人的には使う機会は少ないのかと思っています。

### ClusterIPを作成する(--type=ClusterIP)
ClusterIPはリソースを作成すると自動で振られます。`--cluster-ip=''`を利用することで指定もできますが、意識しないことが寛容です。
下記の例では`--type=ClsuterIP`を指定していますが、デフォルト値なので指定は不要です。

```sh
$ kubectl expose deploy/my-nginx --type=ClusterIP --port=8080 --target-port=80 --dry-run -o yaml
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    run: my-nginx
  name: my-nginx
spec:
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 80
  selector:
    run: my-nginx
  type: ClusterIP
status:
  loadBalancer: {}

$ kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP    29d
my-nginx     ClusterIP   10.104.190.5   <none>        8080/TCP   87s
```

### NodePortを作成する(--type=NodePort)
NodePortを作成します。ClusterIPとNodePortはリソースを作成すると自動的に振られます。
NodePortは管理せずに自動で振られるものを利用する方が適しているかと思いますが、変更が必要な場合は`kubectl edit svc my-np`などで`svc.spec.ports.nodePort`を直接変更すると早いです。

```sh
$ kubectl expose deploy/my-nginx --type=NodePort --port=80 --name=my-np --dry-run -o yaml 
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    run: my-nginx
  name: my-np
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    run: my-nginx
  type: NodePort
status:
  loadBalancer: {}

$ kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP        29d
my-nginx     ClusterIP   10.104.190.5   <none>        8080/TCP       5m32s
my-np        NodePort    10.97.199.41   <none>        80:30541/TCP   72s
```

### LoadBalancerを作成する(--type=LoadBalancer)
ローカルで勉強している際にはあまり使う機会がない（？）と思いますが、今までと同じ手法で作成することができます。外部LBとの連携がないためEXTERNAL-IPはpending stateですが、なにかしら設定したい場合は`--external-ip=''`で付与することもできます。

```sh
$ kubectl expose deploy/my-nginx --type=LoadBalancer --port=80 --target-port=8888 --name=my-lb --dry-run -o yaml
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    run: my-nginx
  name: my-lb
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8888
  selector:
    run: my-nginx
  type: LoadBalancer
status:
  loadBalancer: {}

$ kg svc
NAME         TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP      10.96.0.1      <none>        443/TCP        29d
my-lb        LoadBalancer   10.96.155.99   <pending>     80:31957/TCP   20s
my-nginx     ClusterIP      10.104.190.5   <none>        8080/TCP       10m
my-np        NodePort       10.97.199.41   <none>        80:30541/TCP   6m1s
```


# kubectl create
`kubectl create XXX`は多くのリソースをワンライナーで作成することができます。表現できないYAMLがあっても出力結果を少しいじれば多くのケースで対応でき、とても便利です。

```sh
$ kubectl create -h | grep Ava -A 15
Available Commands:
  clusterrole         Create a ClusterRole.
  clusterrolebinding  Create a ClusterRoleBinding for a particular ClusterRole
  configmap           Create a configmap from a local file, directory or literal value
  cronjob             Create a cronjob with the specified name.
  deployment          Create a deployment with the specified name.
  job                 Create a job with the specified name.
  namespace           Create a namespace with the specified name
  poddisruptionbudget Create a pod disruption budget with the specified name.
  priorityclass       Create a priorityclass with the specified name.
  quota               Create a quota with the specified name.
  role                Create a role with single rule.
  rolebinding         Create a RoleBinding for a particular Role or ClusterRole
  secret              Create a secret using specified subcommand
  service             Create a service using specified subcommand.
  serviceaccount      Create a service account with the specified name
```

deployment, job, cronjob, serviceなどkubectl run/exposeで作成できたものもkubectl createから作成できますが、オプションが少なく実用的ではないかと思います。

### namespaceを作成する
namespaceは以下のように作成します。

```sh
$ kubectl create ns test
namespace/test created

$ kubectl get ns test
NAME   STATUS   AGE
test   Active   11s
```

### ConfigMap, Secretを作成する
ConfigMapは環境変数を以下の方法で読み込めます。

| Option | Sample | Description |
|------------|------------|------------|
| --from-env-file='' | --from-env-file=test.env | key=valueで記載された環境変数ファイルを読み込みます。|
| --from-file='' | --from-file=path/dir/, --from-file=env.txt, --from-file=key1=env.txt | ファイル名をkey、もしくはkeyを指定してvalueとしてファイルを読み込みます。|
| --from-literal='' | --from-literal=key1=value1 --from-literal=key2=value2 | コマンド上で読み込む環境変数を設定します。複数設定したい場合は`--from-literal`を環境変数のkey=valueの１セットで追加していく必要があります。|

この中でも事前にファイルを用意する必要がないので`--from-literal=''`は非常に便利なコマンドです。

```sh
$ kubectl create cm test-cm --from-literal=key1=value1 --from-literal=key2=value2 --dry-run -o yaml
apiVersion: v1
data:
  key1: value1
  key2: value2
kind: ConfigMap
metadata:
  creationTimestamp: null
  name: test-cm
```

Secretの場合は作成する内容によって`docker-registry`、`generic`、`tls`の選択は必要ですが、考え方はConfigMapと同じです。
また、base64 encodingを地震でする必要がないので楽です。

```sh
$ kubectl create secret generic test-secret --from-literal=key1=value1 --from-literal=key2=value2 --dry-run -o yaml
apiVersion: v1
data:
  key1: dmFsdWUx
  key2: dmFsdWUy
kind: Secret
metadata:
  creationTimestamp: null
  name: test-secret

$ echo "dmFsdWUx" | base64 -d
value1
```

### ServiceAccount, role(clusterrole), rolebinding(clusterrolebinding)を作成する
権限関連です。これらは作成するリソースが多いので、YAMLファイルのテンプレートを引っ張ってくるのは手間がかかります。
しかしkubectl createではワンライナーでほとんどを作成することができます。

例えばjohnというserviceaccountにwatcherというroleを付与したい場合は３コマンドで済みます。

```sh
$ kubectl create sa john
serviceaccount/john created

$ kubectl create role watcher --verb=get,list,watch --resource=pod,deploy 
role.rbac.authorization.k8s.io/watcher created

$ kubectl create rolebinding john-watcherer --role=watch --serviceaccount=test:john 
rolebinding.rbac.authorization.k8s.io/john-watcherer created

$ kubectl auth can-i list pod --as system:serviceaccount:test:john
yes

$ kubectl auth can-i list pod --as system:serviceaccount:default:john
no

$ kubectl auth can-i create pod --as system:serviceaccount:test:john
no

$ kubectl auth can-i watch deploy --as system:serviceaccount:test:john
yes
```

これをYAMLで実現しようとすると非常に手間がかかります。

```sh
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: null
  name: john
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  creationTimestamp: null
  name: watcher
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - extensions
  resources:
  - deployments
  verbs:
  - get
  - list
  - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  creationTimestamp: null
  name: john-watcher
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: watcher
subjects:
- kind: ServiceAccount
  name: john
  namespace: test
```

# まとめ
結構長くなりましたが、ぜひ勉強を始めたばかりの方に共有したいと思いまとめました。
間違いなどあれば指摘もらえると嬉しいです。

ありがとうございました。