---
# System prepended metadata

title: k8s

---

# k8s
## Architecture
![](https://hackmd.io/_uploads/rkn64Jsvh.png)

### Control plane
> 透過以下這些功能組件，控制平面控制著k8s內所有的工作節點與資源

- kube-apiserver
api-server專門處裡內外網的要求，假如你用command下指令，api-server就會接收到指令並執行。
node和node之間的溝通也是透過api-server。
- etcd
用於保存k8s集群資料的資料庫，如果集群出事，就會透過etcd的資料來復原。
- kube-scheduler
根據你集群的健康度和資源來分配新的pod要放在哪個地方。
- kube-controller-manager
包含節點控制器(node Controller) 和 工作控制器(job Controller)等控制器的管理。
例如節點出現故障時，就會發出通知。

### Node
![](https://hackmd.io/_uploads/BJOKHyjv2.png)
> 節點(node)被控制平面(control plane)管理著，而節點當中放著多個pod以及其內部執行的app，因此可以看出節點負責提供pod所需的環境並管理著他們。
- Kubelet
在每個節點上運行的node-agent，負責管理node內pod的狀態並確保容器運行在pod上。
- Container Runtime
負責容器的運行，k8s支援任何實作k8s-CRI的Container Runtime，例如Docker, containerd, CRI-O。
- Kube-proxy
依照需求生成iptables，並將流量轉導到正確的pod上。

### Pod
> pod是k8s中能創建和管理，最小的計算元件。
> 類似一組共享namespaces和filesystem volumes的container。

通常會用以下兩種來運行pod
- pod運行單一容器
將pod當成單一容器wrapper
- pod運行多個彼此之間需要協同工作的容器
一個pod內會封裝需要共享資源以及緊密協作的應用程式。
舉例來說，其中一個容器將文件以共享卷(shared volume)的方式分享出去，同時另一個邊車(side-car)容器 刷新以及更新這些文件，而pod則將這些容器包裹成一個單獨個體。

#### pod的資源
>既然要運行應用程式，那是必須要使用某種資源，實際上，容器在pod當中的確共享著兩種資源，網路(networking)和儲存卷(volume):
- 網路
在k8s中，每個pod都會分到一組專用的ip地址，也就是所謂的Pod IP，而在這個pod當中的容器則共享這些網路 資源，例如host-name,IP-address和port。
- 儲存卷
使用者會看情況給pod分配一組儲存卷(volume)，在這個pod內的容器可以使用該儲存卷，藉此達到共享數據。同時儲存卷還可以保存這些數據，在容器重啟或被刪除後也能保證數據不遺失，達成持久化的效果。

#### pod的管理與控制
> 不會直接對pod進行部屬或操作，而是會透過控制器(controller)來對pod進行操作。雖說叫做控制器，不過並不是有個元件叫做控制器，而是多種類型的物件，下面是主要常見的控制器
- Deployment:
告訴k8s如何去建立pod的實體，透過ReplicaSets來控制pod更新，rolling-update等行為。
- ReplicaSets
確保pod的數量以及版本，透過deployment和ReplicaSets，可以達成rolling-update和roll-back等操作。
- DaemonSet
用來確保節點(node)上會運行指定pod，當有節點加入叢集(cluster)時，就會在節點上生成一個pod。而當節點從叢集被移除時，pod也會被回收。通常會用於監控用或是log收集用。
- StatefulSets
當pod有需要穩定,唯一,持久化的需求時，就需要透過statefulSets來部屬。例如你希望能夠建立一個持久化的redis，不要因為重啟pod將redis內的資料刪除，就可以考慮用StatefulSets部屬.
- ~~ReplicationController~~
(官方建議用Deployment和ReplicaSet 來取代他)
基本功能和ReplicaSets差不多，都是確保pod的數量，太多砍掉，太少增加。唯一和ReplicaSets的差別是ReplicationController只支援equality-based label selector。

## 單機版K8s
### Kind
![](https://hackmd.io/_uploads/BkNgT1sDn.png)
> 用docker在本機運行kubernetes，kind會將docker的容器當成節點(node)來運行

### how to install KIND
1. install docker.io
2. install kind
```shell=
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.11.1/kind-linux-amd64
```
3. create kind cluster
```shell=
kind create cluster
kind get clusters
kind delete cluster
kind create cluster --config kind-example-config.yaml
```
4. install kubectl
https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/

### Create pod
```shell=
kubectl run nginx --image=nginx --restart=Never
```
### Check pod
```shell=
kubectl get pods
kubectl describe pod nginx
```
### Delete pod
```shell=
kubectl delete pod nginx
```
### 透過yaml建立範例pod
```yaml=
# redis.yaml
apiVersion: v1
kind: Pod
metadata:
  name: redis
spec:
  containers:
  - name: redis
    image: redis
```
- apiVersion
這是目前k8s該元件的版本號，以這個範例的kind為pod，則他的版本號就是v1(代表第一個穩定釋出的版本)。
如果要用其他元件，則版本號可以從官網的Kubernetes API去查詢。
- Kind
指定的元件類型，在這邊就是pod，k8s判斷你建立的是service還是pod也是看這邊。
- metadata
下面會有name labels 以及 annotations， name就是這個pod叫什麼名字，label會在後面章節提到，主要是讓k8s去分配管理用，而annotations則是單純註解，不支援查找跟分類。
- spec
定義容器內容，在這邊是指定名稱為redis，並使用redis image。

### 建立pod
```shell=
kubectl create -f redis_example.yaml
kubectl exec -it redis bash
```

### port-forward
```shell=
kubectl port-forward redis 8787:6379
```

### service
> a Service is an abstraction which defines a logical set of Pods and a policy by which to access them.
> 簡單來說就是在pod外擋一層虛擬層，方便另一組pod來認識這組pod群，不必認識單個單個pod。
> service的工作是提供外部能夠找到pod群當中能用的pod，當只有單個pod的時候，壞掉或是更版也是全部都壞掉跟更版，不會有負載平衡的效果。

Service example:
```yaml=
apiVersion: v1
kind: Pod
metadata:
  name: my-redis
  labels:
    app: redis
spec:
  containers:
  - name: pod-redis
    image: redis
    ports:
    - containerPort: 6379
```

```shell=
kubectl expose pod my-redis --type=LoadBalancer --name=my-redis-service --port=7788 --target-port=6379
kubectl get pods
```

### ReplicaSet
> 因為service的工作是提供外部能夠找到pod群當中能用的pod，當只有單個pod的時候，壞掉或是更版也是全部都壞掉跟更版，不會有負載平衡的效果。因此，就該Deployment 和 ReplicaSet 出場了。

> ReplicaSet確保一組穩定的replica Pods運行，會確保這些pod會在資源許可下，會啟動你要求的數量。
官方說法，建議ReplicaSet搭配deployment使用，並且透過deployment運行，而非直接操作ReplicaSet。

#### Create replicaSet
```yaml=
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # modify replicas according to your case
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google_samples/gb-frontend:v3
```
#### ReplicaSet get and delete
```shell=
kubectl get rs
kubectl get pods
kubectl delete rs frontend
```

### Deployment
```yaml=
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
```
```shell=
kubectl get deployments
kubectl delete deployment nginx-deployment # metadata name
```
### 更新Deployment內image 版本
```shell=
kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1 --record
```
### Strategy

#### RollingUpdate
```yaml=
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rollingupdate-strategy-example
  labels:
    app: nginx
spec:
  strategy:
    type: RollingUpdate # 升級策略
    rollingUpdate:
      maxSurge: 2 # 表示會等建立兩個新pod後才刪除一個舊pod
      maxUnavailable: 1 # 最多可以有幾個pod在無法使用狀態
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
```
#### Recreate
```yaml=
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rollingupdate-strategy-example
  labels:
    app: nginx
spec:
  strategy:
    type: Recreate # Recreate會先將pod都關閉，再建立新的pod。
```

### StatefulSets
> 需要用到StatefulSets的情況沒那麼常見，但是當你服務擁有以下要求，就會需要StatefulSets了：

1. 穩定&獨立的網路標籤(固定的pod name或 host name)
2. 穩定的persistent storage(當關掉電源後資料不會因此消失，還是能存取相同資料的儲存庫，揮發式儲存庫的相反(Volatile storage 當電源關閉後，資料就會消失))
3. 有序 逐一緩慢地擴充(scaling)和部屬(deployment)的pod
4. 有序 自動地滾動更新(rolling updates)的pod上面這些情境都適合StatefulSets。

#### Limitaion
1. pod的儲存必須PersistentVolume Provisioner基於storage class或者管理者事先提供。
2. 基於資料安全，擴充(scaling)以及刪除不會刪掉相關的儲存卷(volumes)。
3. 需要Headless Service來處理pod的network identity。
```yaml=
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx # has to match .spec.template.metadata.labels
  serviceName: "nginx"
  replicas: 3 # by default is 1
  template:
    metadata:
      labels:
        app: nginx # has to match .spec.selector.matchLabels
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi
```

### Redis 範例
先建立一個headless service
```yaml=
apiVersion: v1
kind: Service
metadata:
  name: redis-service
  labels:
    app: redis
spec:
  ports:
  - name: redis-port
    port: 6379
  clusterIP: None
  selector:
    app: redis
    appCluster: redis-cluster
```
在建立有 statefulset 的 redis
```yaml=
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  selector:
    matchLabels:
      app: redis
  serviceName: "redis"
  replicas: 1
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis
        command: ["redis-server", "--appendonly", "yes"] # redis要持久化，除了pod設定改成statefulset外，也必須特別設定持久化。
        ports:
        - containerPort: 6379
          name: web
        volumeMounts:
        - name: redis-aof
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: redis-aof
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "gp2"
      resources:
        requests:
          storage: 1Gi
```
> RDB(Redis DataBasa file):
已指定間隔對資料進行snapshot儲存，然後在進行操作時，會fork出一個process進行操作，fork完成後主process就可以不受影響，不過由於其間隔複製的特性，有時會丟失資料。

> AOF(Append-Only File):
透過fsync策略進行備份，有三種模式可以選：
>1. 無fsync
>2. 一秒鐘一次fsync(預設)
>3. 每次執行寫入時fsync

### DaemonSet
>DaemonSet會確保所有nodes上運行著特定功能的pod。當nodes加入cluster時就會在它們上面新增一個pod，當nodes從cluster上被移除時，那些pod也會被移除。如果DaemonSet被刪掉的話，pod也會跟著一起被刪掉。

下面是某些daemonsets的使用範例：

在nodes上面運行cluster storage的daemon，例如glusterd或ceph
在node上運行收集log的daemon，例如Fluentd 或 logstash
在node上運行監控node的daemon，例如Prometheus Node Exporter collectd 或者 Datadog agent等等

```yaml=
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-elasticsearch
  namespace: kube-system
  labels:
    k8s-app: fluentd-logging
spec:
  selector:
    matchLabels:
      name: fluentd-elasticsearch
  template:
    metadata:
      labels:
        name: fluentd-elasticsearch
    spec:
      tolerations:
      # this toleration is to have the daemonset runnable on master nodes
      # remove it if your masters can't run pods
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      containers:
      - name: fluentd-elasticsearch
        image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
```

check status
```shell=
kubectl apply -f daemonset.yaml
kubectl get pods -l name=fluentd-elasticsearch -A
kubectl get pods -l name=fluentd-elasticsearch -A -o wide
kubectl get pods -n kube-system
```

### NameSpace
>k8s支援在同個物理的cluster上面建立多個虛擬的cluster，這些cluster就是Namespaces。
什麼情況下適合使用Namespace呢，當一個環境同時被多個團隊所使用，或者在使用雲端平台時，避免浪費錢，將同個cluster切成開發和正式環境。
- default
沒有指定Namespace時，預設被賦予的Namespace。
- kube-system
k8s系統建立出來的物件，會被放入這個Namespace內。
- kube-public
可被所有使用者看到的共用Namespace。
- kube-node-lease
這個Namespace擁有各個node的lease物件。node的lease物件允許kubelet送出心跳，讓control plane偵測到node是否有掛掉。

#### Create/ get
```shell=
kubectl create namespace k8stest
kubectl get namespaces
```

> Note: 在1.21版本，control plane會在所有Namespace上設置一個不可變的label kubernetes.io/metadata.name，而label的值則是Namespace名稱。

### Label

label是一個能加到object上的key/value組合，通常會用識別對使用者有意義以及相關的object，就像下面這樣：
```json=
"metadata": {
  "labels": {
    "key1" : "value1",
    "key2" : "value2"
  }
}
```

> 不過要注意，label通常用於有效的查詢和監控，理想上適用於UI以及CLI。而如果是沒有辨識性的資訊則建議使用Annotations


### Label selectors
label所使用的選擇器(selectors)，他能過濾label，目前api支援兩種組合：equality-based 和 set-based
1.equality-based:
就是常見的判斷式，有 =, !=, ==
2.set-based:
通常用以多組label搜尋，有 in notin 和 exists
以下是範例:
```shell=
kubectl get pods -l environment=production
kubectl get pods -l `environment in (production, qa)`
```

## Practice Note

### A Note on Editing Existing Pods
1. Extract the definition to a file 
`kubectl get pod <pod-name> -o yaml > pod-definition.yaml`
2. Then edit the file to make the necessary changes, delete and re-create the pod.
`kubectl edit pod <pod-name>`

### Imperative command

`kubectl run nginx --image=nginx --dry-run=client -o yaml > nginx-pod.yaml`


#### Create an NGINX Pod

`kubectl run nginx --image=nginx`

#### Generate POD Manifest YAML file (-o yaml). Don't create it(--dry-run)

`kubectl run nginx --image=nginx --dry-run=client -o yaml`

#### Create a deployment

`kubectl create deployment --image=nginx nginx`

#### Generate Deployment YAML file (-o yaml). Don't create it(--dry-run)

`kubectl create deployment --image=nginx nginx --dry-run -o yaml`



#### Generate Deployment with 4 Replicas

`kubectl create deployment nginx --image=nginx --replicas=4`



#### kubectl scale command

`kubectl scale deployment nginx --replicas=4`

> Another way to do this is to save the YAML definition to a file and modify

`kubectl create deployment nginx --image=nginx--dry-run=client -o yaml > nginx-deployment.yaml`


#### Create a Service named redis-service of type ClusterIP to expose pod redis on port 6379

`kubectl expose pod redis --port=6379 --name redis-service --dry-run=client -o yaml`

> This will automatically use the pod's labels as selectors


#### Create a Service named nginx of type NodePort to expose pod nginx's port 80 on port 30080 on the nodes:

`kubectl expose pod nginx --port=80 --name nginx-service --type=NodePort --dry-run=client -o yaml`
`kubectl create service nodeport nginx --tcp=80:80 --node-port=30080 --dry-run=client -o yaml`


### Configmaps and secret
> 基本上只要把configMag改成secret就可以了

整個ENV的給法
```yaml=
envFrom:
  - configMapRef:
      name: app-config
```

整個ENV某key的給法
```yaml=
env:
  - name: APP_COLOR
    valueFrom:
      ConfigMapKeyRef:
        name: app-config
        key: APP_COLOR
```
```yaml=
volumes:
  - name: app-config-volume
    configMap:
      name: app-config
```

> Note:環境變數的變更是沒辦法直接online更改, 可以先把pod刪掉在重長，也可以利用replace的方式重建
`kubectl replace --force -f {yaml}`

### Docker Container Security


```yaml=
apiVersion: v1
kind: Pod
metadata:
  name: app-config
spec:
  containers:
    - name: ubuntu
      image: ubuntu
      command: ["sleep", "3600"]
      securityContext:
        runAsUser: 1000
        capabilities:
          add: ["MAC_ADMIN"]
```
> 如果你的securityContext寫在container底下, 那就只會套用到container.

> specs.serviceAccountName


## Network
### NetworkPolicy
```bash=
kubectl get netpol
```
```yaml=
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: internal-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      name: internal
  policyTypes:
    - Egress
    - Ingress
  ingress:
    - {}
  egress:
    - to:
      - podSelector:
          matchLabels:
            name: payroll
      ports:
        - protocol: TCP
          port: 8080

    - to:
      - podSelector:
          matchLabels:
            name: mysql
      ports:
        - protocol: TCP
          port: 3306
```