# k8s
## Architecture

### 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

> 節點(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

> 用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
```