---
# System prepended metadata

title: K8S (Ku bé)

---

# K8S (Ku bé)
## Introduction
### Cách phát âm
* Kiu-bơ-nấy-tịt-s
* Kiu-bơ-nà-tịt-s
* Kiu-bơ-nà-địt-s
### Các vấn đề khi chạy docker ở production
* mỗi lần muốn có cập nhật mới thì ta phải docker compose down xong rồi up lại, khoảng thời gian giữa lúc down và up thì app của chúng ta bị shutdown, không hoạt động được
* auto scale app lên được theo những thông số cụ thể
* deploy xong thấy có lỗi thì làm sao ngay lập tức quay lại bản cũ
* Còn nữa
### Định nghĩa
* K8S là "container orchestration" - hệ thống điều phối container giúp tự động hoá việc triển khai (deploy), scale và quản lý (manage) các ứng dụng chạy trên nền container.
* So sánh với docker swarm
    * với những app và kiến trúc phức tạp thì K8S làm tốt và phù hợp hơn, trong khi Docker Swarm hướng tới sự đơn giản khi sử dụng.
* Phiên bản Enterprise của nó là Openshift
### Giúp ích gì
* tự biết cách deploy và quản lý app
* tự biết cách check logs, check deployment, auto scale,...
* kết hợp với CICD để tự động hoá toàn bộ quá trình triển khai app
* biết cấu hình các thứ ABCXYZ
* Biết K8S nó cho mình làm việc độc lập hơn rất là nhiều, từ viết Dockerfile, rồi viết file manifest K8S để deploy.

# Ku bé Cluster
{
K8S Cluster : 1 tập hợp nhiều node được kết nối với nhau
node : 1 máy, 1 VPS, hay 1 server
}

* Môi trường works on : Ku bé Cluster 
* k8S điều phối các activities trong cluster:
    * start, stop, restart, kill các container của bạn
    * quản lý volume
    * phân quyền
    * đưa ra quyết định khi nào cần scale app của bạn lên
    * load balance
(Từ từ sẽ hiểu sau)
## Node
* **Nodes, Control Plane $\epsilon$ Cluster**
![](https://hackmd.io/_uploads/SJcf15ce6.png)
### Control Plane (Master node)
* Brain of the Cluster
* Quản lý cluster, deploy, scale app
### Node
* Có thể là máy ảo VPS, physical PC
* Mỗi node làm nhiệm vụ như worker bên trong cluster vậy, kiểu như anh công nhân, bên trên - Control Plane bảo gì thì làm nấy
#### Node component
##### Kubelet
* trưởng phòng, agent, chịu trách nhiệm giao tiếp với bên Control Plan thông qua K8S API
* lắng nghe thông tin các Pod, dùng nhiều cơ chế để đảm bảo việc tạo ra các container đúng theo mô tả trong podspec được chạy (running) và không bị lỗi (healthy)
* Nó cũng đồng thời cập nhật trạng thái của các container trên node cũng như thông tin của node về control plane.
* Ví dụ đơn giản nhất là nếu kubelet bị stop thì node đó sẽ hiển thị trạng thái NotReady trong cluster.
##### kube-proxy
* network-proxy chạy trên từng node. Nó quản lý và duy trì các network rule trên các node.
##### Container Runtime
* Container Runtime là thứ để chạy container
* Trc đó dùng docker, sau đó dùng containerd
#### Additional
* Deploy app lên K8S cluster = control plane triển khai app on node in cluster
* Note: Cluster chạy cho production nên có tối thiểu 3 nodes để đảm bảo tính high-availability
* Control Plane chạy trên node gọi là master node
![](https://hackmd.io/_uploads/B1Yrwcix6.png)
* 2 loại chính là Master Node (còn gọi là Control Plane) và Worker Node (Data Plane)
* worker node: cung cấp tài nguyên cho các app chạy
* Master node nếu cần thì cũng dc cấu hình thành worker node
* trong nhiều tình huống thực tế ta vẫn phải chạy master node đồng thời là worker node.
#### Master node
* kube-api-server
* etcd
* kube-scheduler
* kube-controller-manager
##### kube-api-server
* kube-api-server cung cấp ra các API của Kubernetes
##### etcd
* một CSDL dạng key-value, dùng lưu data của cluster
* data: quản lý tài nguyên của k8s : thông tin node, pod, service, deployment, configmap,...
* phải có kế hoạch backup DB định kỳ.
##### kube-scheduler
* Kiểu như con (pods) sinh ra mà chưa tìm thấy mẹ (gán vào node), thì thằng này là thám tử để tìm mẹ phù hợp cho thằng con (cluster là bệnh viện Hùng Vương =)))
* tham số ảnh hưởng tới việc điều phối một Pod vào một node:
    * Các yêu cầu về tài nguyên cho Pod như RAM/CPU..
    * Các ràng buộc về phần cứng phần mềm
    * Các chỉ định về affinity và anti-affinity.. và nhiều yếu tố khác nữa
##### kube-controler-manager
* tổng hợp của nhiều controller khác nhưng chạy chung 1 process:
    * Node controller: quản lý xem có node nào bị down ko
    * Job controller: quản lý các job/tasks, sau đó tạo pods để chạy job.
    * Endpoints controller: quản lý các endpoints việc giao tiếp giữa pods và service
    * Service Account và Token controller: Tạo ra các account và token API mặc định cho namespace mới.
##### cloud-controller-manager (sử dụng với cloud)
#### Thành phần Addon
##### DNS
* DNS server lưu trữ các bản ghi cho các service của kubernetes.
* hầu hết chúng ta làm việc với các service qua service name, và đương nhiên thì để làm được việc đó thì k8s cần có DNS.
##### Web UI (Dashboard)
* Ngoài sử dụng dashboard, thì có nhiều giải pháp tương tự cũng khá phổ biến như sử dụng Rancher, Lens hay k9s.. để có giao diện quản trị với k8s cluster.
* theo dõi,quản lý cũng như troubleshoot các ứng dụng chạy trên cluster và chính tình trạng của cluster
## Inside the Node
* Khi deploy app, app run on the node of cluster, inside a place/instance called Pod (Pọt)
* Pod = instance nhỏ nhất ta can deploy app, monitor pod.
![](https://hackmd.io/_uploads/Bki6W59ea.png)
**Big Note: 1 node can Run nhiều Pod => can run 1 or nhiều container**
![](https://hackmd.io/_uploads/HyR9f5cx6.png)

* Pod có cả Volume, containerized app
* Xác định Pod = Gán IP (cluster IP) Ví dụ trên: 10.10.10.x

### Pod
* 1 Pod run in 1 node in cluster, trong 1 pod là 1 tập hợp của 1 hay nhiều containers, các containers đó sẽ dùng chung storage (volume) và network.
* Có **file template** để guide cách chạy các containers
* naked pod: chạy trực tiếp, pod sẽ không được chạy lại nếu như bị failed.
* cách phổ biến nhất : dùng những resource khác như Deployment hay Job để khởi tạo nó, đây cũng là cách mà ta nên dùng.
![](https://hackmd.io/_uploads/S1bbNyWZT.png)
![](https://hackmd.io/_uploads/SyMt4kWZ6.png)
* Pod như 1 nhà kho chứa container
* ![](https://hackmd.io/_uploads/SkBrBJ--p.png)
* cung cấp cho chúng ta thêm nhiều chức năng để quản lý và chạy một container, giúp container của ta chạy tốt hơn là chạy container trực tiếp, như là group tài nguyên của container, check container healthy và restart
* kubernetes sẽ quản lý Pod thay vì quản lý container trực tiếp
##### Expose pod
* Khi tạo pod thì pod chỉ giao tiếp trong local, phải expose pod mới có thể chọc vào từ bên ngoài
* ![](https://hackmd.io/_uploads/rJ_zegWbT.png)
* ![](https://hackmd.io/_uploads/HypMxlW-p.png)

##### Tổ chức pod bằng cách sử dụng labels
* Dùng label là cách để chúng ta có thể phân chia các pod khác nhau tùy thuộc vào dự án hoặc môi trường. Ví dụ công ty của chúng ta có 3 môi trường là testing, staging, production, nếu chạy pod mà không có đánh label thì chúng ta rất khó để biết pod nào thuộc môi trường nào
* Labels là một thuộc tính cặp key-value mà chúng ta gán vào resource ở phần metadata
* ![](https://hackmd.io/_uploads/HJ2DPg-Z6.png)
```yaml
  labels:
    enviroment: staging # label with key is enviroment and value is staging
    project: kubernetes-series
```
#### Giải thích file template trong pod:
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.19
    ports:
    - containerPort: 80
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

```
* template file có tên gọi phổ biến hơn là **manifest file**
* file ở **dạng YAML**, đuôi yml hoặc yaml đều được
* Ở đầu của mọi manifest file ta cần phải có **apiVersion, kind và metadata**
* **spec** là nơi ta chỉ định **hưỡng dẫn Pod chạy các container**, như các bạn thấy bên trong ta có trường containers là 1 array
* Ở trên ta chỉ có 1 **item trong array** -> 1 container trong pod
* Image là tên image của container, cái này cần phải chính xác
* Bên dưới ta có định nghĩa thêm phần **resource** cần thiết cho pod này nữa, **requests** là ta "xin" bao nhiêu cho pod (tối thiểu), limits là tối đa pod được "ăn" bao nhiêu. Lí do: limit resource cho namespace nên buộc ta phải có cái này.

#### Additional
* 1 pod chỉ nên chạy 1 container, vì các container trong pod sẽ "tightly couple", dính chặt chẽ với nhau, scale sẽ khó, khi 1 container trên pod thay đổi thì cả pod sẽ được deploy lại

### Vòng đời của Pod (Pod lifecycle)
#### Tạo Pod
![](https://hackmd.io/_uploads/rJs2Zijxp.png)
* Chi tiết luồng tạo Pod:
1. Khi ta thực hiện tạo một Pod mới, thông tường là dùng lệnh kubectl để apply một file yaml là file mô tả chi thiết các thông tin cần thiết cho việc tạo Pod (kubectl apply -f nginx-pod.yaml). Bản chất lệnh kubectl sẽ làm việc với api-server để gọi một api tương ứng cho việc tạo Pod.
2. API server validate yaml file, ok thì ghi data vào etcd. Hệ thống ghi nhận 1 pod mới cần tạo. Sau khi ghi xong vào etcd, phản hồi lại cho client pod đã dc tạo.
3. scheduler sẽ check API server xem có gì mới ko, các pod mới tạo có dc gán vào node nào chưa. Nó phát hiện ra pods mới và tìm node thỏa mãn yêu cầu pod nêu ra và done (ví dụ node1). Thông tin node chạy trên sẽ gửi cho API server
4. API server nhận được thông tin Pod mới được gán vào node1 thì thực hiện update thông tin này và etcd. Lúc nào pod ở trạng thái bound.
5. Đến lượt kubelet trong node ra tay, nó phát hiện node ở trạng thái bound dc bốc lịch vào node (lấy thông tin từ api-server). Nó lấy các thông tin định nghĩ từ pod để chạy container trên node. Sau đó, update lại status cho api-server
6. API sau đó nhận dc tin từ kubelet, ghi vào etcd. Sau đó nó gửi bản tin acknowledgement tới kubelete để báo rằng event này đã được chấp nhận.
#### luồng xóa Pod:
1. Người dùng gửi lệnh để xóa Pod
2. Đối tượng Pod trên k8s được cập nhật trạng thái thành "dead" sau một khoảng thời gian gọi là grace-time
*  Các hành động sau diễn ra song song:
*  Pod sẽ hiện thị ở trạng thái "Terminating" khi được kiểm tra từ phía client
*  Kubelet thấy một Pod được đánh dấu là Terminating thì nó bắt đầu thực hiện dừng process của Pod
*  Endpoint controller theo dõi pod đã được xóa chưa để xóa thông tin pod đó khỏi các endpoint mà nó phục vụ
*  Nếu pod có định nghĩa một preStop hook, thì nó được gọi tới bên trong pod. Nếu preStop hook vẫn đang chạy mà grace-time đã hết, thì bước (2) sẽ lại được gọi với thời gian grace-time nới thêm là 2 giây. Các bạn có thể tìm hiểu thêm về "Container hook" ở đây.
*  Process bên trong Pod đã được gửi tín hiệu yêu cầu terminate (TERM signal)
*  Sau khi grace-time kết thúc, thì mọi process bên trong Pod sẽ bị kill bởi SIGKILL.
*  Kubelet hoàn thành xóa Pod bằng cách gọi API server và set grace-time bằng 0, nghĩa là yêu cầu xóa ngay lập tức. Lúc này Pod sẽ không còn và client sẽ không thể thấy được Pod này nữa.


### Namespace
* Namespace là cách để ta chia tài nguyên của cluster, và nhóm tất cả những resource liên quan lại với nhau, bạn có thể hiểu namespace như là một sub-cluster.
* vài namespace đã được tại bởi kube, trong đó có namespace tên là default, kube-system
* Cách tổ chức namespace tốt là tạo theo 
```
<project_name>:<enviroment>
```
* Có thể xem các pod trong namespace
    
### ReplicationControllers
* resource mà sẽ tạo và quản lý pod, và chắc chắn là số lượng pod nó quản lý không thay đổi và kept running
* quản lý pod thông qua labels của pod
* Tạo # of pods = replicas
#### Why
* pod nó sẽ giám sát container và tự động restart lại container khi nó fail
* ![](https://hackmd.io/_uploads/HymxVxZZT.png)
* trường hợp toàn bộ worker node của chúng ta fail thì sẽ thế nào? pod nó sẽ không thể chạy nữa, và application của chúng ta sẽ downtime với người dùng
* chạy cluster với hơn 1 worker node, RC sẽ giúp chúng ta giải quyết vấn đề này
* How: Nếu replicas = 1 thì rc sẽ tạo và giám sát 1 pod, nếu 1 node bị die kéo theo pod thì rc sẽ xem pod mình quản lý có trong node đó ko, nếu có thì tạo ra 1 pod y chang ở node khác.
* ![](https://hackmd.io/_uploads/HyV9J---p.png)
* cách hđ:
* ![](https://hackmd.io/_uploads/SJSakWZZ6.png)
    * TH too few là khi xảy ra node bị die kéo theo pod, khi nó mới dc khởi tạo,...
    * TH too many khi ta tạo template mới và muốn run rc theo template mới, ta muốn scale down replicas,...
#### use case
* có thể tăng performance của ứng dụng bằng cách chỉ định số lượng replicas trong RC để RC tạo ra nhiều pod chạy cùng một version của ứng dụng.
* ![](https://hackmd.io/_uploads/ByGMZ--b6.png)
#### Thực hành
* Cấu trúc:
* ![](https://hackmd.io/_uploads/BkbP-ZWWa.png)
* ![](https://hackmd.io/_uploads/S14Y-Zb-p.png)

##### Các câu lệnh
##### Thay đổi template của pod
* có thể thay đổi template của pod và cập nhật lại RC, nhưng nó sẽ không apply cho những thằng pod hiện tại, muốn pod của bạn cập nhật template mới, bạn phải 
* 1. xóa hết pod để RC tạo ra pod mới 
* 2. xóa RC và tạo lại
![](https://hackmd.io/_uploads/r1ikGWZbT.png)

### ReplicaSets thay thế RC
* RS và RC sẽ hoạt động tương tự nhau. Nhưng RS linh hoạt hơn ở phần label selector, trong khi label selector thằng RC chỉ có thể chọn pod mà hoàn toàn giống với label nó chỉ định, thì thằng RS sẽ cho phép dùng một số expressions hoặc matching để chọn pod nó quản lý.
* Ví dụ, thằng RC không thể nào match với pod mà có env=production và env=testing cùng lúc được, trong khi thằng RS có thể, bằng cách chỉ định label selector như env=* 
* 4 operators: In, NotIn, Exists, DoesNotExist
```yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: myapp
spec:
  replicas: 4
  selector:
    matchLabels:
      app: myapp-pod
  template:
    metadata:
      labels:
        app: myapp-pod
    spec:
      containers:
      - name: myapp-container
        image: nginx:1.19.10

```
* Thường thì ta rất ít khi, hay cụ thể là gần như không bao giờ phải tự tay tạo ReplicaSet, thay vào đó ta nên dùng Deployment, và Deployment sẽ tự làm điều đó và quản lý nó thay ta
### DaemonSets để chạy chính xác một pod trên một worker node
* DaemonSets này sẽ deploy tới mỗi thằng node một pod duy nhất, và chắc chắn có bao nhiêu node sẽ có mấy nhiêu pod, nó sẽ không có thuộc tính replicas
* Ứng dụng của thằng DaemonSets này sẽ được dùng trong việc logging và monitoring.
* ![](https://hackmd.io/_uploads/SyqNQbZ-a.png)


### Ku bé Services (cho các chị)
![](https://hackmd.io/_uploads/r1VhheN-a.png)

* resouce sẽ tạo ra một single, constant point của một nhóm Pod phía sau nó.
* Client sẽ mở connection tới service, và connection đó sẽ được dẫn tới một trong những Pod ở phía sau.
* ![](https://hackmd.io/_uploads/H1PLSmZ-6.png)
* **Vậy service giúp ta những vấn đề gì? Mỗi thằng Pod nó cũng có địa chỉ IP riêng của nó, sao ta không gọi thẳng nó luôn mà thông qua service chi cho mất công?**
* Pods are ephemeral: 
    * Khi tạo thằng pod mới tạo ra, nó sẽ có một IP khác với thằng cũ. Nếu ta dùng IP của Pod để tạo connection tới client thì lúc Pod được thay thế với IP khác thì ta phải update lại code.
* Multiple Pod run same application
    * ta sẽ có nhiều pod đang chạy một ứng dụng của chúng ta để tăng performance. Ví dụ khi ta dùng ReplicaSet với replicas = 3, nó sẽ tạo ra 3 Pod. **Vậy làm sao ta biết được nên gửi request tới Pod nào?**
    *  Kubernetes cung cấp cho chúng ta Services resource. Service sẽ tạo ra một endpoint không đổi cho các Pod phía sau, client chỉ cần tương tác với endpoint này.

* Services cũng sẽ sử dụng label selectors để chọn Pod mà nó quản lý connection.
![](https://hackmd.io/_uploads/B17fvQ-W6.png)

#### ClusterIP (địa chỉ IP ảo)
* ![](https://hackmd.io/_uploads/r1VI6eN-6.png)
* service mặc định
* Đây là loại service sẽ tạo một IP và local DNS mà sẽ có thể truy cập ở bên trong cluster
*  không thể truy cập từ bên ngoài
* tưởng tượng Service nó như kiểu "tổ trưởng dân phố" vậy, người nắm giữ thông tin các cư dân trong phố của mình, nếu ai muốn hỏi gì thì có thể hỏi trực tiếp ông
* Trước khi dùng services cho các pods thì thêm labels cho các pods
* Problem: Phải lấy clusterIP của pods mới gọi vào dc, nếu pods đổi IP thì sao???
* Ví dụ File cấu hình service:
```yaml
apiVersion: v1
kind: Service
metadata:
  name: helloworld
spec:
  type: ClusterIP
  ports:
    - name: http
      protocol: TCP
      port: 3000
      targetPort: 80
  selector:
    app.kubernetes.io/name: helloworld

```
* Note:
    * type=ClusterIP: mở cho service này chỉ được truy cập trong cluster
    * protocol=TCP: chỉ cho phép traffic là TCP (các dạng request http thông thường mà ta vẫn hay gọi), hoặc cũng có thể có UDP hay ICMP
    * port=3000: định nghĩa cho Service này 1 cái port, thì lát nữa ở nơi khác gọi vào service thì sẽ là Service_name:3000
    * targetPort=80: đây là cái containerPort của Helloworld mà service này sẽ target vào
    * selector: áp dụng service này cho 1 tập hợp pod được chỉ định có các labels như ta định nghĩa
* Có vài trường hợp bạn có thể sử dụng Kubernetes Proxy để kết nối service của bạn :
    * Debug service, hoặc kết nối chúng trực tiếp từ laptop vì một vài lí do nào đó
    * Cho phép các internal traffic, etc
* Các service chỉ tồn tại ở một namespace 
![](https://hackmd.io/_uploads/H1_PlXzWa.png)
* Format goi ten: ```<tên_service>:<port_của_svc>```
* Pod của ta tha hồ restart, scale thoải mái, ta cứ dùng tên service thì vẫn gọi được bình thường. Và đây cũng là cách ta thường làm thật ở production - luôn có service "đứng trước" pod của chúng ta, giao tiếp với những nơi khác đều qua Service.
#### Có thể tạo nhiều port cho service
* Tương tự thì cũng có thể tạo nhiều port cho Service để target được vào nhiều port của container trong Pod Hello World nhé (targetPod)
#### Cách hay hơn để tạo targetPort
*  người viết cấu hình manifest cho Service cần biết được là "à container HelloWorld có containerPort=80", và từ đó cho Service target vào port 80.
*  Và cứ mỗi khi containerPort thay đổi thì targetPort bên Service cũng phải thay đổi theo.
*  Cách: đặt tên cho containerPort để chỉ cần dùng tên đó cho service

#### NodePort
* ![](https://hackmd.io/_uploads/S1J_gZ4-p.png)
* NodePort Service là một trong những cách nguyên thủy nhất đễ kết nối external traffic trực tiếp tới service của bạn. NodePort như cái tên đã ngụ ý, sẽ mở một port trên tất cả các Nodes(VMs), băt cứ traffic nào tới các node này sẽ được chuyển tiếp đến service.
* YAML file:
```yaml
apiVersion: v1
kind: Service
metadata:  
  name: my-nodeport-service
spec:
  selector:    
    app: my-app
  type: NodePort
  ports:  
  - name: http
    port: 80
    targetPort: 80
    nodePort: 30036
    protocol: TCP

```
* Khi nào bạn nên sử dụng NodePort?
    * Bạn chỉ có thể có một service với mỗi port
    * Bạn chỉ có thể sử dụng port từ 30000–32767
    * Nếu địa chỉ IP của Node, VM thay đổi, bạn sẽ phải xử lí điều đó.

#### LoadBalancer
* ![](https://hackmd.io/_uploads/SkRDB-VZ6.png)
* có thể gửi hầu hết các loại traffic đến nó, như HTTP, TCP, UDP, WebSockets, gRPC.
* Khi nào bạn nên sử dụng LoadBalancer?
    * Một điểm trừ lớn đó là mỗi service bạn expose với một LoadBalancer sẽ lấy luốn IP address của nó, và bạn sẽ phải chi trả cho LoadBalancer với mỗi service được exposed. Điều đó rất đắt đỏ.
    *  ta cũng được 1 public IP , IP đó là IP của load balancer.
    * chú ý là khi ta set type=LoadBalancer thì K8S cũng sẽ cấp cho Service của chúng ta ClusterIP và expose ra NodePort.
* YAML
```yaml
apiVersion: v1
kind: Service
metadata:
  name: helloworld
spec:
  type: LoadBalancer # ---- > ở đây
  ports:
    - name: http
      protocol: TCP
      port: 3000
      targetPort: myport
  selector:
    app.kubernetes.io/name: helloworld

```

#### Ingress
* Ingress không thực sự là một loại service. Thay vào đó, nó đứng trước nhiều services, và hoạt động như một smart router hoặc entry point tới cluster của bạn.
* ![](https://hackmd.io/_uploads/BkPn8Z4Za.png)
```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-ingress
spec:
  backend:
    serviceName: other
    servicePort: 8080
  rules:
  - host: foo.mydomain.com
    http:
      paths:
      - backend:
          serviceName: foo
          servicePort: 8080
  - host: mydomain.com
    http:
      paths:
      - path: /bar/*
        backend:
          serviceName: bar
          servicePort: 8080

```
* Khi nào bạn nên sử dụng Ingress?
    * Ingress có lẽ là cách hiệu quả nhất để expose các service của bạn, nhưng cũng có thể là cách phức tạp nhất.
    * Ingress là component hữu ích nhất nếu bạn muốn hiển thị nhiều dịch vụ dưới cùng một địa chỉ IP và các dịch vụ này đều sử dụng cùng một giao thức L7 (thường là HTTP).

### Deployment
#### Tạo deployment
##### Viết file manifest
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
  labels:
    app: myapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myapp-pod
  template:
    metadata:
      labels:
        app: myapp-pod
    spec:
      containers:
      - name: myapp-container
        image: nginx:1.14.2
        ports:
        - containerPort: 80
          name: http
        resources:
          requests:
            memory: "64Mi"
            cpu: "64m"
          limits:
            memory: "128Mi"
            cpu: "128m"

---
apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
spec:
  type: LoadBalancer
  ports:
    - name: first-port
      protocol: TCP
      port: 8000
      targetPort: http
  selector:
    app: myapp-pod

```
*  Deployment + Service như kiểu cặp bài trùng luôn đi cùng với nhau. 1 cái để deploy pod, 1 cái để expose Pod để nơi khác có thể gọi vào.
* Các thành phần trong file:
    * Dấu '---' dùng để ngăn cách service và deployment nếu ta muốn viết chung trong 1 file
    * kind: Deployment, đặt label là app:myapp
    * spec:
        * replicas : 2 => replica sets tạo 2 pods và quản lý nó
        * selector: label selector cho Pod
        * matchLabels: app: myapp-pod => "deployment" này sẽ được apply cho các pod có label match với giá trị app: myapp-pod
        * template: nơi ta định nghĩa spec cho pod
        * labels: định nghĩa metadata với labels giống với giá trị cho pod label selector ở trên
        * Sau đó cấu hình container giống naked pod: tên container, image là gì, expose port 80 (ta cũng đặt tên "phụ" cho nó là http), và thêm resource request.

* Sau đó, ta apply:
> kubectl apply -f myapp.yml --kubeconfig=kubernetes-config
* Check trạng thái của deployment:
> kubectl get deploy --kubeconfig=kubernetes-config
```
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
myapp-deployment   2/2     2            2           8s
```
* READY: đã có bao nhiêu pod đã "ready" cho user gọi vào, như ta thấy thì cả 2/2 pod đã ready
* UP-TO-DATE: bao nhiêu pod đã được update để đạt trạng thái mong muốn (2)
* AVAILABLE giống READY

* Note: mỗi lần deployment chạy lại là 1 lần rollout
> kubectl rollout status -w deployment/myapp-deployment --kubeconfig=kubernetes-config
deployment "myapp-deployment" successfully rolled out
* Deployment đã tạo ra 1 rs để quản lý pods
```
kubectl get rs --kubeconfig=kubernetes-config

------>>

NAME                          DESIRED   CURRENT   READY   AGE
myapp-deployment-55f998b87b   2         2         2       3m6s

```
* Get service để truy cập thử app ở external
```
kubectl get svc --kubeconfig=kubernetes-config

---

NAME        TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)          AGE
myapp-svc   LoadBalancer   10.245.136.215   146.190.7.21   8000:30623/TCP   44m

```

#### Vọc vạch
```
kubectl set image deploy/myapp-deployment myapp-container=nginx:1.16.1 --kubeconfig=kubernetes-config

---

deployment.apps/myapp-deployment image updated

```
* Đổi image của container nginx lên 1.16 từ terminal
* Các diễn biến ở deployment:
    * ![](https://hackmd.io/_uploads/r1m9zw9b6.png)
    * ngay sau khi set image thì K8S sẽ ngay lập tức triển khai pod mới, nhưng các pod cũ vẫn giữ nguyên, đảm bảo trong thời gian đó app của chúng ta không có downtime, user vẫn có thể truy cập thoải mái.
    * Sau khi deploy pod mới thành công thì K8S sẽ teardown, terminate dần pod cũ đi.
    * 1 TH: Chú ý rằng trong quá trình deploy, bất kì Pod mới nào READY là cũng sẽ nhận được traffic đẩy vào. Nên có thể dẫn tới trường hợp là trong 1 khoảng thời gian ngắn (1 vài giây), lúc mà Deployment đang update thì cùng lúc tồn tại cả pod cũ và pod mới, dẫn tới việc cùng 1 user nhưng 2 requests có thể đi vào 2 pod chạy 2 version cũ/mới khác nhau.
* Ngoài cách update trực tiếp từ terminal thì có thể sửa lại pod spec ở file manifest và apply lại
* Hoặc dùng command sau: 
> kubectl edit deploy myapp-deployment --kubeconfig=kubernetes-config

#### Scale pods in rs
#### Rollback về version cũ
```
 kubectl rollout history deploy/myapp-deployment --kubeconfig=kubernetes-config

----


REVISION  CHANGE-CAUSE
1         <none>
2         <none>
3         <none>
```
* check rollout history xem từ đầu tới giờ ta đã deploy mấy lần 
> kubectl rollout undo deploy/myapp-deployment --kubeconfig=kubernetes-config
* Rollback lại version trc đó
* K8S sẽ tiến hành tạo các pod mới với image v1.16 và sau đó Terminate các pod v1.19
* ![](https://hackmd.io/_uploads/Bk7zBv9WT.png)
> kubectl rollout undo deploy/myapp-deployment --kubeconfig=kubernetes-config --to-revision=2
* Thêm --to-revision=2 để chọn version


#### Additional
* Apply từ 1 url:
> kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.4.0/deploy/static/provider/do/deploy.yaml
* apply file manifest tạo ingress nginx trực tiếp từ Github
* imagePullSecret để pull image từ private hub
* Xoá Deployment
    * Xóa trực tiếp
        * kubectl delete deploy myapp-deployment --kubeconfig=kubernetes-config
    * Xóa qua manifest
        * kubectl delete -f myapp.yml --kubeconfig=kubernetes-config
* Có thể để các name và label giống nhau
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  replicas: 4
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: nginx:1.19.10
        ports:
        - containerPort: 80
          name: http
        resources:
          requests:
            memory: "64Mi"
            cpu: "64m"
          limits:
            memory: "128Mi"
            cpu: "128m"

---
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  type: LoadBalancer
  ports:
    - name: http
      protocol: TCP
      port: 8000
      targetPort: http
  selector:
    app: myapp

```

### Configmap và Secret trên Kubernetes
#### ConfigMap
##### Viết yaml file configMap
configmap.yml
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config
data:
  app_name: "Hello World"
  introduction.txt: |
    My Introduction

```
* Các thành phần:
    * kind: configMap
    * app_name là key có value là Hello World
    * introduction.txt là key (1 file) có nội dung/ key là My Introduction
* key-value data
* dùng để lưu data ko bảo mật 
* Có thể lưu string, num, boolean, file text
* Thường dùng cho biến mtr

> kubectl apply -f configmap.yml  --kubeconfig=kubernetes-config
* Apply configMap này
> kubectl get configmap myapp-config -o yaml --kubeconfig=kubernetes-config
* Check xem nội dung bên trong configMap
![](https://hackmd.io/_uploads/HJpUros-T.png)

##### Viết yaml file cho deployment
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
  labels:
    app: myapp
spec:
  selector:
    matchLabels:
      app: myapp-pod
  template:
    metadata:
      labels:
        app: myapp-pod
    spec:
      securityContext:
        runAsUser: 1001
        runAsGroup: 1001
        fsGroup: 1001
      containers:
      - name: myapp-container
        image: maitrungduc1410/configmap-and-secret
        ports:
        - containerPort: 3000
          name: http
        resources:
          requests:
            memory: "128Mi"
            cpu: "64m"
          limits:
            memory: "750Mi"
            cpu: "500m"
        env:
          - name: APP_NAME
            valueFrom:
              configMapKeyRef:
                name: myapp-config
                key: app_name
        volumeMounts:
        - name: config
          mountPath: "/app/storage"
          readOnly: true
      volumes:
      - name: config
        configMap:
          name: myapp-config
          items:
          - key: "introduction.txt"
            path: "introduction.txt"

---
apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
spec:
  type: LoadBalancer
  ports:
    - name: http
      protocol: TCP
      port: 8080
      targetPort: http
  selector:
    app: myapp-pod

```
* các thành phần:
    * securityContext: dùng để define User,group chạy container, nó sẽ thay cái thằng USER ta define o83 dockerfile
    * env: env trong container
        * biến APP_NAME lấy giá trị từ ConfigMap có tên là myapp-config và lấy key là app_name để truyền value cho biến APP_NAME
    * volumes: volume này là của tất cả container dùng chung (ở pod), lấy từ configmap có tên myapp-config và lấy key map nó vào 1 file introduction.txt trong volume
    * volumeMounts: volume của containers, tham chiếu từ volume của pod vào 
        * name: tên của volume của pod
        * mountPath: "/app/storage" : mount vào path trong container
        * readOnly: true : ko cho container ghi đè vào volume này
    
> kubectl apply -f myapp.yml --kubeconfig=kubernetes-config
* Kiểm tra các thành phần trong container ta đã tạo:
> kubectl exec -it myapp-deployment-995b4d567-8p4fr --kubeconfig=kubernetes-config -- sh
* Chui vào container
> whoami
id -u
id -g
id
* Check securityContext
```
ls -la

echo $APP_NAME

ls -la storage

cat storage/introduction.txt

```
* Check có biến APP_NAME và file introduction.txt ta đã mount từ volume vào chưa

```
kubectl get svc --kubeconfig=kubernetes-config

--->>>

NAME        TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)          AGE
myapp-svc   LoadBalancer   10.245.64.155   167.99.30.23   8080:32101/TCP   3m20s

```
* Get service và chạy ở địa chỉ: 167.99.30.23:8080

#### Secret
##### Viết yaml file cho Secret
```yaml
apiVersion: v1
kind: Secret
metadata:
  name: myapp-secret
type: Opaque
stringData:
  EMAIL: "admin@test.com"
  PASSWORD: "123456"
  secret.txt: |
    My supersecret

```
* Các thành phần:
    * type: Opaque => thường dùng
    * stringData: data kiểu string
    * Secret có 2 kiểu là stringData (string) và data (base64) 
    * nếu ta dùng stringData, thì lát nữa sau khi apply xong và get thì K8S sẽ show data dạng base64 thôi.
```yaml
kubectl apply -f secret.yml --kubeconfig=kubernetes-config

--->>>

secret/myapp-secret created

```
* Apply secret
```
kubectl get secret --kubeconfig=kubernetes-config

--->>>

NAME                  TYPE                                  DATA   AGE
default-token-4lggr   kubernetes.io/service-account-token   3      10m
myapp-secret          Opaque                                3      41s

```
* get secret

##### Viết yaml cho deployment
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
  labels:
    app: myapp
spec:
  selector:
    matchLabels:
      app: myapp-pod
  template:
    metadata:
      labels:
        app: myapp-pod
    spec:
      securityContext:
        runAsUser: 1001
        runAsGroup: 1001
        fsGroup: 1001
      containers:
      - name: myapp-container
        image: maitrungduc1410/configmap-and-secret
        ports:
        - containerPort: 3000
          name: http
        resources:
          requests:
            memory: "128Mi"
            cpu: "64m"
          limits:
            memory: "750Mi"
            cpu: "500m"
        env:
          - name: APP_NAME
            valueFrom:
              configMapKeyRef:
                name: myapp-config
                key: app_name
          - name: EMAIL
            valueFrom:
              secretKeyRef:
                name: myapp-secret
                key: EMAIL
          - name: PASSWORD
            valueFrom:
              secretKeyRef:
                name: myapp-secret
                key: PASSWORD
        volumeMounts:
        - name: config
          mountPath: "/app/storage/introduction.txt"
          subPath: introduction.txt
          readOnly: true
        - name: secret
          mountPath: "/app/storage/secret.txt"
          subPath: secret.txt
          readOnly: true
      volumes:
      - name: config
        configMap:
          name: myapp-config
          items:
          - key: "introduction.txt"
            path: "introduction.txt"
      - name: secret
        secret:
          secretName: myapp-secret
          items:
            - key: secret.txt
              path: secret.txt
---
apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
spec:
  type: LoadBalancer
  ports:
    - name: http
      protocol: TCP
      port: 8080
      targetPort: http
  selector:
    app: myapp-pod

```

```yaml
        env:
          - name: APP_NAME
            valueFrom:
              configMapKeyRef:
                name: myapp-config
                key: app_name
          - name: EMAIL
            valueFrom:
              secretKeyRef:
                name: myapp-secret
                key: EMAIL
          - name: PASSWORD
            valueFrom:
              secretKeyRef:
                name: myapp-secret
                key: PASSWORD
        volumeMounts:
        - name: config
          mountPath: "/app/storage/introduction.txt"
          subPath: introduction.txt
          readOnly: true
        - name: secret
          mountPath: "/app/storage/secret.txt"
          subPath: secret.txt
          readOnly: true
      volumes:
      - name: config
        configMap:
          name: myapp-config
          items:
          - key: "introduction.txt"
            path: "introduction.txt"
      - name: secret
        secret:
          secretName: myapp-secret
          items:
            - key: secret.txt
              path: secret.txt
```
* phân tích đoạn khác với configmap:
    * env: ta định nghĩa thêm 2 biến mtr là EMAIL và PASSWORD, những info quan trọng từ secret 
    * volumes:
        * thêm 1 volume nữa là secret, có name là myapp-secret, truyền vào file secret.txt
    * volumeMounts:
        * subPath => là cái path mà ta định nghĩa ở phần volumes bên dưới
        * k8s ko cho phép mountPath ở các volume giống nhau => bắt buộc phải dùng thêm subPath để lấy path từ volume chung

> kubectl apply -f myapp.yml --kubeconfig=kubernetes-config
* apply deployment
##### Note:
* Lí do volume của ta không được update "realtime" vào trong container nữa là bởi vì hiện tại ta dùng subPath để mount, và như vậy ta sẽ không nhận được update mới nữa mà phải restart lại deployment.
> kubectl rollout restart deploy myapp-deployment --kubeconfig=kubernetes-config
* reset deployment
#### Lấy toàn bộ secret vào truyền thẳng vào biến môi trường
* thường ta phải định nghĩ từng biến mtr và truyền từ đâu,...
* tổ chức các file secret/configmap:
    * file chỉ chứa biến môi trường ra 1 kiểu:
```yaml
apiVersion: v1
kind: Secret
metadata:
  name: myapp-secret
type: Opaque
stringData:
  EMAIL: "newadmin@gmail.com"
  PASSWORD: "abcdefg"

```
* còn lại nếu file có chứa nội dung dài, dạng text bất kì thì đưa ra thành các configmap/secret riêng:
```yaml
apiVersion: v1
kind: Secret
metadata:
  name: myapp-secret
type: Opaque
stringData:
  EMAIL: "newadmin@gmail.com"
  PASSWORD: "abcdefg"

```
```yaml
      containers:
      - name: myapp-container
        image: maitrungduc1410/configmap-and-secret
        envFrom:
        - secretRef:
            name: myapp-secret
        - configMapRef:
            name: myapp-config

```

### PersistentVolume và PersistentVolumeClaim trong Kubernetes
* Có 2 loại mà ta hay sử dụng nhất:
    * PersistentVolume: volume ko bị cook khi pod destroyed, có thể mount sang pod khác
    * Ephemeral Volume: bay màu khi pod destroyed, pod restart vẫn còn
* Mỗi lần container bị crash, lỗi,... -> kubelet restart lại container -> mất toàn bộ data -> dùng volume
* PersistentVolume (PV): sống dai thành huyền thoại fee fai
* PersistentVolumeClaim (PVC): là 1 request để "xin" được sử dụng storage. Pod muốn sử dụng volume thì cần phải có Claim. 
    * Có thể cấu hình read/write/read-write cho volume.
* MQH mập mờ (bound): 1 PVC & nhiều PV ; 1 PV & 1 PVC (one-to-many)

* yaml file example:
```yaml
# PV
apiVersion: v1
kind: PersistentVolume
metadata:
  name: block-pv
spec:
  capacity:
    storage: 10Gi # tối đa 10GB
  accessModes:
    - ReadWriteOnce
    
 ---
 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: block-pvc
spec:
  accessModes:
    - ReadWriteOnce # read + write nhưng chỉ dành cho 1 Node trên cluster
  resources:
    requests:
      storage: 10Gi # "xin" hết 10 GB

```
* Các thành phần:
    * accessModes: ReadWriteOnce => 1 node được dùng PV, PVC

* StorageClass là nơi cấp cho ta PV, thường thì các cloud provider đều có StorageClass phù hợp với nhu cầu đọc,ghi dữ liệu nhanh hay chậm của ta.

#### Tạo PVC và PV
* Tạo file pvc.yml cho PVC:
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: demo-pvc
spec:
  accessModes: 
    - ReadWriteOnce
  resources:
    requests:
      storage: "2Gi"

```
* Các thành phần:
    * accessModes: ReadWriteOnce (cho phép read+ write)
    * storge: 2 gb 

> kubectl apply -f pvc.yml --kubeconfig=kubernetes-config
* apply pvc
```yaml
kubectl get pvc --kubeconfig=kubernetes-config

------

NAME       STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS       AGE
demo-pvc   Bound    pvc-93715847-269c-40bd-9407-7a11a61514a4   2Gi        RWO            do-block-storage   61s

```
* Các thành phần:
    * pvc có tên là demo-pvc
    * Được bound với pv: pvc-93715847-269c-40bd-9407-7a11a61514a4
    * tạo bởi storage class là do-block-storage (provider tạo sẵn)
* ta dùng các provider lớn, họ sẽ cung cấp sẵn PV được provision (được họ cung cấp và quản lý thay ta), bởi vì những phần cấu hình đó nhiều khi không dễ nên họ sẽ "abstract" phần đó luôn cho ta.
* PV là cluster-object, tức là nó không thuộc về namespace, get PV show toàn bộ PV ở cluster
#### Mount PVC vào Pod
* Viết file yaml deployment:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demoapp
  labels:
    app.kubernetes.io/name: viblo-pv-demo
spec:
  selector:
    matchLabels:
      app: demoapp
  template:
    metadata:
      labels:
        app: demoapp
    spec:
      securityContext: # --> ở đây
        fsGroup: 1001 # --> và đây
      containers:
      - name: demoapp
        image: maitrungduc1410/viblo-k8s-pv-demo:latest
        ports:
        - containerPort: 3000
          name: http
        resources:
          requests:
            memory: "128Mi"
            cpu: "64m"
          limits:
            memory: "750Mi"
            cpu: "500m"
        volumeMounts:
          - name: data
            mountPath: /app/storage
            # readOnly: true
            
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: demo-pvc
            # readOnly: true # nếu như ta muốn chỉ cho phép read ko write


``` 
* Các thành phần:
    * volumes: ta tạo 1 volume tên data lấy dữ liệu từ pvc có tên demo-pvc
    * voulmeMounts: mount data từ volume data vào mountPath là /app/storage
    * persistentVolumeClaim ta cũng có thể thêm readOnly: true, nó sẽ ghi đè lên readOnly ở phần volumeMounts
    * securityContext:
        * fsgroup
        * cái này áp dụng cho tất cả container trong pod
        * Nếu ko chạy này thì có khi mount file volume vào thì có thể bị lỗi permissions
> kubectl apply -f deployment.yml --kubeconfig=kubernetes-config
#### RECLAIM POLICY
```
kubectl get pv --kubeconfig=kubernetes-config

------

NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                      STORAGECLASS       REASON   AGE
pvc-1f23bfc0-5d62-432c-8706-84bc72b1680f   2Gi        RWO            Delete           Bound    learnk8s-bde84e/demo-pvc   do-block-storage            24m

```
* có thể có 3 giá trị:
    * Delete(mặc định): xoá PV cùng với PVC khi PVC bị xoá
    * Retain: xoá PVC, PV vẫn còn. Trường hợp này thường dùng khi data của ta quan trọng, ví dụ database
    * Recycle: deprecated (lỗi thời)
> kubectl patch pv pvc-1f23bfc0-5d62-432c-8706-84bc72b1680f -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' --kubeconfig=kubernetes-config
* Thay đổi reclaim policy từ delete sang retain (phải có quyền quản lý cluster)
#### Readonly Volume
* thêm readOnly : True vào volumeMounts trong container
#### Additional
1. lưu lượng data vượt quá capacity PVC
![](https://hackmd.io/_uploads/Bk_NRf0Wa.png)
* Nó sẽ báo lỗi, ko thể write nữa
2. App đang chạy có xoá được PVC
* PVC sẽ không bị xoá nếu vẫn còn đang được sử dụng bởi bất kì Pod nào, kể cả nếu admin có cố gắng xoá PV trong lúc đó thì PVC cũng không bị xoá
> kubectl delete pvc demo-pvc --kubeconfig=kubernetes-config
3. Tăng capacity của PVC
* PVC sẽ không resize cho tới khi nó được Bound vào 1 Pod nào đó, và ngay sau khi Pod của ta lên sóng, thì PVC được bound -> resize từ 1 -> 2Gi
* Ở đây ta đang test với trường hợp ta resize PVC trước cả khi ta tạo Deployment. Giờ nếu ta resize khi ta đã có Deployment và app đang chạy ngon thì kịch bản cũng tương tự, ta phải restart Deployment thì PVC mới được update.
4. Giảm capacity của PVC thì data có bị mất
```
kubectl apply -f pvc.yml --kubeconfig=kubernetes-config

------

The PersistentVolumeClaim "demo-pvc" is invalid: spec.resources.requests.storage: Forbidden: field can not be less than previous value

```
* không thể resize xuống nhỏ hơn capacity cũ, tức là kể cả lượng data của ta có ít hơn 1 Gi, nhưng ta cũng không thể resize từ 2Gi xuống 1Gi, điều này nhằm đảm bảo data không bị mất trong quá trình resize
