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