Certified Kubernetes Administrator (CKA) - Phần 1: Core Concepts

Cluster Architecture

Cụm Kubernetes bao gồm một tập hợp các node, có thể là physical or virtual, oncloud hoăc onpremise lưu trữ các ứng dụng ở dạng container.

Các node worker trong cụm giống như những con tàu có khả năng chở hàng hóa.

Tuy nhiên, cần có người để chất hàng lên những con tàu này, không chỉ đơn thuần là chất hàng, mà còn phải lên kế hoạch cách chất hàng, xác định con tàu phù hợp, lưu trữ thông tin về các con tàu, theo dõi và giám sát vị trí của các container trên tàu, quản lý toàn bộ quá trình chất hàng, v.v.

Điều này được thực hiện bởi các con tàu điều khiển, thiết bị giám sát, thiết bị thông tin liên lạc, cần cẩu để di chuyển container giữa các tàu, v.v. Các tàu này liên quan đến node master ở trong cụm Kubernetes. Master node chịu trách nhiệm để quản lý cụm, lưu trữ thông tin liên quan đến các node khác nhau, lập kế hoạch container này sẽ đi đâu, giám sát các node và containers trên chúng.

Node master thực hiện tất cả những điều này bằng cách sử dụng một bộ các components với nhau gọi là Control Plane Components.

Bây giờ có rất nhiều container được chất tải và và dỡ hàng khỏi tàu hàng ngày và vì vậy bạn cần duy trì thông tin về các tàu khác nhau, container nào trên tàu nào và nó được tạo vào lúc nào, v.v. Tất cả những thứ này được lưu trữ dưới dạng key:value có tính sẵn sàng cao đươc gọi là etcd.

Etcd là cơ sở dữ liệu lưu trữ thông tin ở dạng key:value. Chúng ta sẽ xem xét kỹ hơn về etcd cluster là gì, dữ liệu nào được lưu trữ trong đó và cách nó lưu trữ dữ liệu.

Khi tàu đến, bạn chất container lên chúng bằng cần cẩu, cần cẩu nhận diện container được đặt lên tàu, nó xác định đúng con tàu dựa trên kích thước của nó, sức chứa của nó, số lượng container đã có trên tàu. Vì vậy, đó là scheduler trong cụm Kubernetes.

Với scheduler nó xác định đúng node để đặt container dựa trên các yêu cầu về tài nguyên container hoặc bất kỳ chính sách hoặc hạn chế nào khác, chẳng hạn như taints.

Node-controller chịu trách nhiệm đưua các node mới vào cụm, xử lý các tình huống khi các node không khả dụng hoặc bị phá hủy.

Replication-controller đảm bảo số lượng container mong muốn đang chạy mọi lúc trong một nhóm replication.

Tiếp đến, làm thế nào để mọi thứ này giao tiếp với nhau?

Kube-apiserver là thành phần quản lý chính của Kubernetes, nó chịu trách nhiệm để điều phối tất cả các hoạt động trong cụm. Nó expose API Kubernetes được sử dụng bởi người dùng bên ngoài để thực hiện các hoạt động quản lý trên cụm, để theo dõi trạng thái của cụm và thực hiện những thay đổi theo yêu cầu, để các node worker liên lạc với máy chủ.

Bây giờ, mọi con tàu đều có thuyền trưởng. Thuyền trưởng phải chịu trách nhiệm để quản lý mọi hoạt động trên các tàu này, có trách nhiệm liên lạc với các chủ tàu bằng việc cho phép người chủ biết rằng họ quan tâm khi tham gia nhóm, nhận thông tin về container được xếp lên tàu và tải các container thích hợp theo yêu cầu, gửi báo cáo lại cho chủ về tình trạng của con tàu này và tình trạng các container trên tàu.

Bây giờ, thuyền trưởng của con tàu là kubelet trong Kubernetes. Kubelet là một tác nhân chạy trên mỗi node trong cụm, nó lắng nghe hướng dẫn từ Kube-apiserver rằng triển khai hoặc phá hủy các container trên các node theo yêu cầu. Kube-apiserver định kỳ tìm nạp báo cáo trạng thái từ kubelet để theo dõi trạng thái của các node và containers trên nó.

Các ứng dụng đang chạy trên các worker node cần có khả năng để liên lạc với nhau.

💡
Ví dụ: Bạn có thể có một web-server đang chạy trong một container trên một node và một database-server đang chạy trên một container khác trên một node khác. Web-server sẽ tiếp cận database-server trên node khác như nào?

Giao tiếp giữa các worker node được kích hoạt bởi một thành phần khác chạy trên mỗi node được gọi là Kube-proxy service. Kube-proxy đảm bảo rằng các quy tắc cần thiết được tuân thủ trên các worker node để cho phép các container chạy trên chúng để kết nối được với nhau.

Vì vậy, tóm lại chúng ta có các node masterworker.

Trên node master, chúng ta có :

  • Etcd cluster: nơi lưu trữ thông tin về cụm.

  • Scheduler: chịu trách nhiệm để lập lịch các ứng dụng hoặc containers trên node.

  • Kube-controller-manager: có các controller khác nhau đảm nhiệm chức năng khác nhau như node-controller, replication-controller, v.v.

  • Kube-apiserver: chịu trách nhiệm để điều phối tất cả các hoạt động trong cụm

Trên node woker, chúng ta có:

  • Kubelet: nhận hướng dẫn từ kube-apiserver và quản lý các container.

  • Kube-proxy: tạo điều kiện cho việc giao tiếp giữa các dịch vụ trong cụm.

Docker-vs-ContainerD

Lúc đầu chỉ có Docker để quản lý container và có những công cụ khác như rkt, nhưng trải nghiệm của người dùng với Docker khiến việc làm việc với container trở nên cực kỳ đơn giản và do đó Docker trở thành công cụ chứa chiếm ưu thế nhất.

Và sau đó Kubernetes đến để điều phối Docker. Vậy Kubernetes đã được xây dựng để điều phối Docker cụ thể ngay từ đầu và hồi đó Kubernetes chỉ làm việc với Docker và không hỗ trợ bất kỳ giải pháp container nào khác. Khi Kubernetes trở nên nổi tiếng với tư cách là người điều phối container và bây giờ các container runtime khác như rkt đều muốn tham gia. Vì vậy người dùng Kubernetes cần nó để hoạt động với container runtime không chỉ là Docker.

Vì thế Kubernetes đã giới thiệu một interface được gọi là Container Runtime Interface (CRI). Vì vậy CRI cho phép bất kỳ nhà cung cấp nào, để hoạt động như container runtime cho Kubernetes miễn là chung tuân thủ các tiêu chuẩn OCI.

OCI viết tắt của Open Container Initiative nó bao gồm:

  • imagespec: thông số kỹ thuật về cách xây dựng một image.

  • runtimespec: xác định các tiêu chuẩn về cách phát triển bất kỳ container runtime nào.

Tuy nhiên Docker không được xây dựng để hỗ trợ các tiêu chuẩn CRI, vì vậy Kubernetes đã giới thiệu dockershim, đó là một cách hacky nhưng tạm thời để tiếp tục hỗ Docker bên ngoài CRI.

Docker bao gồm nhiều công cụ được kết hợp với nhau như Docker CLI, Docker API và các công cụ giúp build image đã có hỗ trợ về volumes, auth, security và cuối cùng là container runtime có tên runc và daemon quản lý runc cái đó được gọi là containerd.

Vậy containerd tương thích với CRI và có thể hoạt động trực tiếp với Kubernetes như tất cả runtime khác. Vì vậy containerd có thể được sử dụng làm thời gian chạy riêng tách khỏi Docker. Vậy là bây giờ đã có containerd như một runtime riêng biệt.

Vì vậy Kubernetes tiếp tục duy trì, hỗ trợ trực tiếp cho công cụ Docker, tuy nhiên phải duy trì dockershim là một nỗ lực không cần thiết và gây thêm nhiều rắc rối nên nó đã được quyết định trong version 1.24 của Kubernetes để loại bỏ hoàn toàn dockershim và do đó hỗ trợ cho Docker đã bị xóa.

CLI - crictl

  • crictl cung cấp CLI cho CRI tương thích với container runtime

  • sử dụng để kiểm tra và debug container runtime

  • hoạt động trên các container runtime khác nhau

$ crictl
$ crictl pull busybox
$ crictl images
$ crictl ps -a
$ crictl exec -i -t 3e025dd50a72d956c4f14881fbb5b1080c9275674e95fb67f965f6478a957d60 ls
$ crictl logs 3e025dd50a72d956c4f1
$ crictl pods

ETCD in Kubernetes

ETCD là một kho lưu trữ dữ liệu dưới dạng key:value phân tán, đáng tin cậy, đơn giản, an toàn và nhanh chóng.

key-value store

KeyValue
NamePhan Văn Hoàng
Age22
LocationHà Nội

Mặc dù bạn có thể lưu trữ và truy xuất key:value đơn giản. Khi dữ liệu của bạn trở nên phức tạp thường sử dụng ở định dạng dữ liệu như JSON hoặc YAML.

{
    "name": "Phan Văn Hoàng",
    "age": 22,
    "location": "Hà Nội"
}

Install ETCD

# Linux
ETCD_VER=v3.4.34

# choose either URL
GOOGLE_URL=https://storage.googleapis.com/etcd
GITHUB_URL=https://github.com/etcd-io/etcd/releases/download
DOWNLOAD_URL=${GOOGLE_URL}

rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
rm -rf /tmp/etcd-download-test && mkdir -p /tmp/etcd-download-test

curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd-download-test --strip-components=1
rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz

/tmp/etcd-download-test/etcd --version
/tmp/etcd-download-test/etcdctl version

Khi bạn chạy etcd, theo mặc định nó khởi động một dịch vụ lắng nghe cổng 2379. Sau đó bạn có thể đính kèm bất kỳ ứng dụng nào vào dịch vụ etcd để lưu trữ và truy xuất thông tin. Bạn có thể sử dụng etcdctl để lưu trữ và truy xuất các cặp key:value.

Để lưu trữ một cặp key:value, chạy lệnh:

$ etcdctl put key1 value1
OK

Để lấy lại dữ liệu đã lưu trữ, chạy lệnh:

$ etcdctl get key1
key1
value1

Kho dữ liệu etcd lưu trữ các thông tin liên quan đến cụm chẳng hạn như nodes, pods,configs, secrets, accounts, roles, bindings, others. Mọi thông tin bạn nhìn thấy khi chạy lệnh kubectl get command là từ etcd server. Mọi thay đổi bạn thực hiện đối với cụm của mình, chẳng hạn như thêm các node bổ sung, triển khai pods, hoặc replicaset được cập nhật trong etcd-server.

Để liệt kê tất cả các keys được Kubernetes lưu trữ, chạy lệnh:

$ kubectl exec etcd-k8s-master -n kube-system -- sh -c \
  "ETCDCTL_API=3 etcdctl get / --prefix --keys-only --limit=10 \
  --cacert /etc/kubernetes/pki/etcd/ca.crt \
  --cert /etc/kubernetes/pki/etcd/server.crt \
  --key /etc/kubernetes/pki/etcd/server.key"

/registry/apiextensions.k8s.io/customresourcedefinitions/ingressclassparams.elbv2.k8s.aws
/registry/apiextensions.k8s.io/customresourcedefinitions/targetgroupbindings.elbv2.k8s.aws
/registry/apiregistration.k8s.io/apiservices/v1.
/registry/apiregistration.k8s.io/apiservices/v1.admissionregistration.k8s.io
/registry/apiregistration.k8s.io/apiservices/v1.apiextensions.k8s.io
/registry/apiregistration.k8s.io/apiservices/v1.apps
/registry/apiregistration.k8s.io/apiservices/v1.authentication.k8s.io
/registry/apiregistration.k8s.io/apiservices/v1.authorization.k8s.io
/registry/apiregistration.k8s.io/apiservices/v1.autoscaling
/registry/apiregistration.k8s.io/apiservices/v1.batch

Kube-apiserver in Kubernetes

Kube-apiserver là thành phần quản lý chinh trong Kubernetes, khi bạn chạy kubectl thì thực chất tiện ích kubectl đang kết nối đến kube-apiserver.

Kube-apiserver trước tiên xác thực yêu cầu và xác nhận nó, sau đó lấy dữ liệu từ etcd cluster và phản hồi lại với thông tin được yêu cầu. Bạn không thực sự cần sử dụng dòng lệnh kubectl, thay vào đó bạn có thể gọi trực tiếp các API bằng cách gửi yêu cầu POST.

Hãy xem một ví dụ về việc tạo pod:

Yêu cầu được authenticated user trước và sau đó nó được validated.

Trong trường hợp này, API server tạo 1 đối tượng pod mà không gán nó cho node nào, thực hiện cập nhật thông tin trong etcd cluster, cập nhật rằng pod đã được tạo.

Scheduler liên tục giám sát kube-apiserver và nhận ra rằng có một pod mới không chỉ định node nào. Scheduler xác định 1 node để đặt pod mới vào và truyền thông tin đó trở lại kube-apiserver. Sau đó kube-apiserver cập nhật thông tin vào etcd cluster.

Kube-apiserver sau đó chuyển thông tin đến kubelet trong node worker thích hợp. Kubelet sau đó tạo pod trên node và hướng dẫn công cụ container runtime để triển khai ứng dụng. Sau khi hoàn tất, kubelet sẽ cập nhật trạng thái đến kube-apiserverkube-apiserver cập nhật thông tin trở lại etcd-cluster.

Trên thực tế, kube-apiserver là thành phần duy nhất tương tác trực tiếp với etcd-cluster. Các thành phần khác như kube-controller-manager, kubelet sử dụng kube-apiserver để thực hiện cập nhật thông tin.

Kube Controller Manager

Controller là một quá trình liên tục giám sát trạng thái của các thành phần khác nhau trong hệ thống và hướng tới việc đưa toàn bộ hệ thống đến trạng thái hoạt động mong muốn.

💡
Ví dụ: Node-controller có trách nhiệm để theo dõi tình trạng của các node và thực hiện các hành động cần thiết để giữ cho ứng dụng chạy, nó thực hiện điều đó thông qua kube-apiserver. Nó thực hiện kiểm tra trạng thái của các node cứ 5s / 1 lần bằng cách đó node-controller có thể theo dõi trạng thái của các node. Nếu node bị dừng thì nó sẽ đánh dấu node không thể truy cập được nhưng nó đợi 40s trước khi đánh dấu là không thể truy cập được và cho 5m để back up.

💡
Ví dụ: Replication-controller nó có nhiệm vụ theo dõi trạng thái của các replicaset và đảm bảo rằng số lượng POD mong muốn. Nếu một POD chết, nó sẽ tạo ra một POD khác.

Đó chỉ là hai ví dụ về controllers, có rất nhiều controller như vậy có sẵn trong Kubernetes. Bất kỳ khái niệm nào chúng ta đã thấy cho đến nay trong Kubernetes, chẳng hạn như deployment, services, namespace,v.v.

Tất cả đều được đóng gói thành mọt process duy nhất được biết đến là kube-controller-manager. Khi bạn cài kube-controller-manager thì các controllers khác nhau cũng được cài đặt.

Kube Scheduler

Trước đó, chúng ta đã thảo luận rằng scheduler trong kubernetes chịu trách nhiệm lập lịch cho các pods trên các nodes. Scheduler chỉ chịu trách nhiệm để quyết định pod nào sẽ đi đến node nào, nó không thực sự đặt pod trên các node đó là công việc của kubelet.

Scheduler chỉ quyết định rằng nhóm sẽ đi đâu, hãy xem cách scheduler thực hiện điều đó.

💡
Trước hết, tại sao bạn cần một scheduler?

Khi có nhiều tàu và nhiều container, bạn muốn chắc chắn rằng container nằm ở đúng con tàu. Có thể có sự khác biệt kích thước của tàu và container, bạn muốn chắc chắn rằng con tàu có đủ năng lực để chứa những container đó.

Các tàu khác nhau có thể đi đến các điểm đến khác nhau, bạn muốn chắc chắn rằng các thùng chứa của bạn được đặt lên đúng tàu để đến đúng đích.

Trong Kubernetes, scheduler quyết định node nào, các pod được đặt tùy thuộc vào các tiêu chí nhất định, bạn có thể có các pod với các yêu cầu tài nguyên khác nhau. Bạn có thể có các node trong cụm dành riêng cho một số ứng dụng nhất định. Vậy scheduler chỉ định các pod này như thế nào?

Scheduler xem xét từng pod và cố gắng tìm node tốt nhất cho nó.

Ở đây POD yêu cầu về CPU, scheduler trải qua hai giai đoạn để xác định node tốt nhất cho pod.

Trong giai đoạn đầu tiên, scheduler cố gắng lọc ra các node không phù hợp với pod này.

💡
Ví dụ: các node không có đủ CPU với yêu cầu của pod, vì vậy hai node nhỏ đầu tiên được lọc ra. Bây giờ chỉ còn lại hai node trên đó pod có thể được đặt.

Bây giờ làm thế nào để scheduler chọn một trong hai, scheduler xếp loại các node để xác định loại phù hợp nhất cho pod, nó sử dụng chức năng ưu tiên để gán điểm cho các node theo thang điểm từ 0 → 10.

💡
Ví dụ: scheduler tính toán số lượng tài nguyên trên các node sau khi đặt pod lên chúng , trong trường hợp này nếu đặt pod lên node thứ 3 thì sẽ còn lại 2 CPU còn node thứ 4 sẽ còn lại 6 CPU. Node4 lớn hơn node3 là 4 CPU nên nó sẽ có được thứ hạng tốt hơn và vì thế nó thắng.

Kubelet

Trước đó, chúng ta đã thảo luận về kubelet. Nó giống như thuyền trưởng trên con tàu, kiểm soát mọi hoạt động trên một con tàu, chịu trách nhiệm làm tất cả các thủ tục giấy tờ cần thiết để trở thành một phần của cụm. Kubelet là đầu mối liên lạc duy nhất từ phía master, họ xếp hoặc dỡ container trên tàu theo hướng dẫn từ scheduler trên master, thực hiện gửi lại các báo cáo về trình trạng của con tàu và container trên đó.

Kubelet trong worker node thực hiện đăng ký node với cụm kubernetes, khi nó nhận được hướng dẫn để tạo 1 container hoặc 1 pod trên node, nó yêu cầu công cụ chạy container runtime có thể là Docker để có thể pull các image theo yêu cầu và chạy ứng dụng. Kubelet sau đó tiếp tục theo dõi trạng thái của pod và các container trong đó rồi báo lại cho kube-apiserver.

Kube Proxy

Trong Kubernetes, mọi pod có thể tiếp cận các pod khác nhau, điều này được thực hiện bằng cách triển khai một giải pháp mạng cho pod trên cụm mà tất cả các pod kết nối tới. Thông qua mạng lưới này, các pod có thể giao tiếp với nhau. Có nhiều giải pháp sẵn có để triển khai một mạng như vậy.

Trong trường hợp này, tôi có một web-application triển khai trên node thứ nhất và db-application triển khai trên node thứ hai. Web-application có thể tiếp cận database chỉ bằng cách sử dụng IP của pod, nhưng khong có gì đảm bảo rằng IP của pod sẽ luôn giữ nguyên. Bạn biết rằng cách tốt hơn cho web-application để truy cập database đang sử dụng service.

Vì vậy, chúng ta tạo ra một service để expose db-application trên toàn cụm, web-application hiện có thể truy cập vào database bàng cách sử dụng tên của service database đó. Dịch vụ này cũng nhận được mội địa chỉ IP được gán cho nó, bất cứ khi nào, một pod cố gắng truy cập service bằng IP của nó hoặc name của service thì nó sẽ chuyển tiếp lưu lượng đến database. Nhưng service này là gì và làm cách nào để có được IP, service có cùng mạng với pod không?

Kube-proxy là một tiến trình chạy trên mỗi node trong cụm Kubernetes, công việc của nó là tìm kiếm các service mới và mỗi khi service mới được tạo ra, nó tạo ra quy tắc thích hợp trên mỗi node để chuyển tiếp lưu lượng truy cập đến các dịch vụ đó đến pod.

Một cách để thực hiện điều này là tạo các rule iptable trên mỗi node trong cụm, để chuyển tiếp lưu lượng hướng tới ip service là 10.96.0.12 đến IP của pod thực tế là 10.32.0.15, đó là cách kube-proxy định cấu hình dịch vụ.

Ví dụ:

Giả sử chúng ta có một Service tên là SVC01 với loại ClusterIP. Khi Service này được tạo ra, API server sẽ kiểm tra xem các Pod nào sẽ được liên kết với Service này. Do đó, nó sẽ tìm kiếm các Pod có nhãn khớp với bộ chọn nhãn (label selector) của Service.

Hãy gọi các Pod này là Pod01 và Pod02. Lúc này, API server sẽ tạo một lớp trừu tượng gọi là endpoint. Mỗi endpoint đại diện cho một địa chỉ IP của một trong các Pod. SVC01 hiện được liên kết với 2 endpoint tương ứng với các Pod của chúng ta. Hãy gọi chúng là EP01 và EP02. Bây giờ, API server ánh xạ địa chỉ IP của SVC01 tới 2 địa chỉ IP, EP01 và EP02.

Chúng tôi muốn ánh xạ này được triển khai trên mạng để lưu lượng truy cập đến IP của SVC01 có thể được chuyển tiếp đến EP01 hoặc EP02.

Để thực hiện điều đó, API server thực hiện ánh xạ mới đến Kube-proxy trên mỗi node, sau đó Kube-proxy sẽ áp dụng ánh xạ này dưới dạng một internal rule.

Demo:

  1. Tạo deployment cho redis

    Tạo file deployment_redis.yaml

     apiVersion: apps/v1
     kind: Deployment
     metadata:
       name: redis
       labels:
         app: redis
     spec:
       replicas: 2
       selector:
         matchLabels:
           app: redis
       template:
         metadata:
           labels:
             app: redis
         spec:
           containers:
           - name: redis
             image: redis
             ports:
             - containerPort: 6379
    

    Triển khai nó với lệnh:

     $ kubectl apply -f deployment_redis.yaml
     deployment.apps/redis created
    
  2. Tạo service

    Tạo file service_redis.yaml:

     apiVersion: v1
     kind: Service
     metadata:
       name: redis
     spec:
       ports:
       - protocol: TCP
         targetPort: 6379
         port: 6379
       selector:  
         app: redis
    

    Triển khai nó với lệnh:

     $ kubectl apply -f redis-service.yaml
     service/redis created
    
  3. Xem cấu hình kube-proxy:

    Kiểm tra endpoint sau khi tạo service, chạy lệnh:

     $ kubectl get ep
     NAME         ENDPOINTS                           AGE
     kubernetes   10.0.1.180:6443                     28h
     redis        192.168.1.8:6379,192.168.1.9:6379   2s
    

    Thực hiện liệt kê các rule IPtables trong một node:

    💡
    Cần ssh vào node trước khi chạy lệnh sau
     $ iptables -t nat -L PREROUTING
     Chain PREROUTING (policy ACCEPT)
     target     prot opt source        destination
     KUBE-SERVICES  all  --  anywhere  anywhere /* kubernetes service portals */
    

    KUBE-SERVICES là một chuỗi được tạo bởi Kube-proxy cho các services

     $ iptables -t nat -L KUBE-SERVICES
     Chain KUBE-SERVICES (2 references)
     target     prot opt source               destination
     KUBE-SVC-OKJCEJEOAS2LLIDR  tcp  --  anywhere     ip-10-105-67-166.ap-southeast-1.compute.internal  /* default/redis cluster IP */
    
     $ iptables -t nat -L KUBE-SVC-OKJCEJEOAS2LLIDR
     Chain KUBE-SVC-OKJCEJEOAS2LLIDR (1 references)
     target     prot opt source               destination
     KUBE-MARK-MASQ  tcp  -- !ip-192-168-0-0.ap-southeast-1.compute.internal/16  ip-10-105-67-166.ap-southeast-1.compute.internal  /* default/redis cluster IP */
     KUBE-SEP-5HCHTHH53GVRAEGJ  all  --  anywhere             anywhere             /* default/redis -> 192.168.1.8:6379 */ statistic mode random probability 0.50000000000
     KUBE-SEP-7VRVVRQDX7NETZTU  all  --  anywhere             anywhere             /* default/redis -> 192.168.1.9:6379 */