avatar

Đóng gói với ứng dụng với container và pods

Trong bài này chúng ta sẽ cùng nhau tìm hiểu về Pod, cách đóng gói ứng dụng với containers và Pod

Đăng vào
13 phút

title

Mục lục

1. Giới thiệu chung

Đây là bài viết thứ hai trong series triển khai ứng dụng spring boot trên Kubernetes. Trong bài này, chúng ta sẽ cùng nhau tìm hiểu về pod, cách đóng gói ứng dụng với containers và pod thông qua những nội dung sau:

  • Đóng gói ứng dụng Spring boot với Docker container.
  • Tạo mới, triển khai pod trên Kubernetes.
  • Nhóm các pod sử dụng labels.
  • Vòng đời của pod.

Các bạn có thể tham khảo source code ở đây và checkout nhánh: series/k8s-springboot/pod.

2. Yêu cầu môi trường

Đầu tiên chúng ta cần cài Docker, có thể kiểm tra quá trình cài đặt Docker bằng lệnh:

docker run hello-world

# Nếu output là message dưới đây thì quá trình cài đặt đã thành công
Hello from Docker!
This message shows that your installation appears to be working correctly.

Chúng ta cũng cần cài đặt Kubernetes ở local, việc này có thể sử dụng Minikube hoặc MicroK8s. Trong series này, chúng ta sẽ sử dụng MicroK8s. Bạn có thể kiểm tra việc cài đặt Kubernetes bằng lệnh:

# Kiểm tra việc cài đặt kubectl sử dụng lệnh:
kubectl cluster-info

Kubernetes control plane is running at https://192.168.1.123:16443

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

# Kiểm tra trạng thái của các nodes, sử dụng lệnh:
kubectl get nodes

NAME   STATUS   ROLES    AGE   VERSION
nbt    Ready    <none>   9d    v1.25.4

3. Đóng gói ứng dụng Spring boot với Docker

Dockerfile, Docker image and Docker containers

Docker là gì?

Docker là một nền tảng giúp xây dựng, kiểm thử, triển khai ứng dụng một cách nhanh chóng bằng cách đóng gói phần mềm với container. Khi cần triển khai ứng dụng trên các môi trường, chúng ta chỉ cần pull docker image của ứng dụng đó và tạo container thì ứng dụng sẽ được khởi chạy ngay lập tức.

Tại sao lại sử dụng Docker?

Docker cho phép tạo ra các môi trường độc lập và tách biệt, điều này sẽ giúp nhất quán về môi trường khi phát triển và triển khai ứng dụng trên các máy khác nhau. Docker có cộng đồng người sử dụng lớn, có nhiều Docker image chất lượng được đóng góp từ cộng đồng và các images này cũng được fix lỗi và cập nhật thường xuyên.

Triển khai Spring boot application với Docker

  1. Tạo Dockerfile và build Docker image từ Dockerfile

    • Đầu tiên cần tạo Dockerfile. Dockerfile chứa các lệnh hướng dẫn Docker tạo ra Docker image. Ví dụ chúng ta có Dockerfile như file dưới đây:
    FROM adoptopenjdk/openjdk11:latest
    WORKDIR /workspace
    COPY target/*-SNAPSHOT.jar /workspace/app.jar
    ENV TZ="Asia/Ho_Chi_Minh"
    EXPOSE 8080
    ENTRYPOINT ["java","-jar","/workspace/app.jar"]
    
  • Dockerfile trên bao gồm các lệnh:

    • FROM: Lệnh này sẽ pull một Docker image (ở đây là adoptopenjdk/openjdk11:latest) để tạo ra một base image layer, các lệnh tiếp theo trong Dockerfile sẽ được thực thi trên base image layer này. Dockerfile bắt buộc phải bắt đầu với FROM.

    • WORKDIR: Các lệnh tiếp theo trong Dockerfile sẽ được thực hiện trong folder mà lệnh WORKDIR định nghĩa, trường hợp này là folder /workspace.

    • COPY: Thực hiện copy file jar ở folder target vào folder /workspace/ bên trong container.

    • ENV: Sử dụng để truyền biến môi trường vào bên trong container, trường hợp này là TZ="Asia/Ho_Chi_Minh".

    • EXPOSE: Sẽ quy định cổng mà container sẽ lắng nghe và tiếp nhận traffic, trường hợp này là port 8080.

    • ENTRYPOINT: Khi container start thì lệnh bên trong ENTRYPOINT sẽ được thực thi, trường hợp này là chạy file jar.

  • Bạn có thể build docker image bằng lệnh sau:

    docker build -t k8s-springboot-series:latest -f Dockerfile .
    
    Sending build context to Docker daemon  18.05MB
    Step 1/6 : FROM adoptopenjdk/openjdk11:latest
    ---> 49877461f3c7
    Step 2/6 : WORKDIR /workspace
    ---> Running in af4cc5d9485b
    Removing intermediate container af4cc5d9485b
    ---> 93bad477e120
    Step 3/6 : COPY target/*-SNAPSHOT.jar /workspace/app.jar
    ---> 08b67e5bfac9
    Step 4/6 : ENV TZ="Asia/Ho_Chi_Minh"
    ---> Running in 87c88aba3e8b
    Removing intermediate container 87c88aba3e8b
    ---> 8482d4c26ef8
    Step 5/6 : EXPOSE 8080
    ---> Running in 19fcfb82ca20
    Removing intermediate container 19fcfb82ca20
    ---> cc97ad8d0a21
    Step 6/6 : ENTRYPOINT ["java","-jar","/workspace/app.jar"]
    ---> Running in 2d87a166c58a
    Removing intermediate container 2d87a166c58a
    ---> 1403dab63194
    Successfully built 1403dab63194
    Successfully tagged k8s-springboot-series:latest
    
  • Kiểm tra docker image bằng lệnh:

    docker images
    
    REPOSITORY                TAG       IMAGE ID       CREATED         SIZE
    k8s-springboot-series     latest    1403dab63194   7 minutes ago   456MB
    
  1. Tạo docker container từ docker image

    Sau bước build docker image từ Dockerfile trên thì bạn đã có một docker image là k8s-springboot-series:latest, giờ bạn có thể chạy một hoặc nhiều docker container từ docker image vừa được build bằng lệnh sau:

    docker run --name k8s-springboot-series -d -p 8080:8080 k8s-springboot-series:latest
    

    Kiểm tra docker container bằng lệnh:

    docker ps
    
    CONTAINER ID   IMAGE                          COMMAND                  CREATED         STATUS         PORTS                                       NAMES
    14330e1c04cb   k8s-springboot-series:latest   "java -jar /workspac…"   3 seconds ago   Up 2 seconds   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   k8s-springboot-series
    
  2. Dùng Postman gọi các API

    API tạo mới Student:

    Endpoint: /api/students, method POST và Request body:

    {
      "fullName": "NGUYEN BA THANH",
      "dateOfBirth": "29/04/1998",
      "hometown": "HA DONG, HA NOI",
      "gender": "MALE"
    }
    

    API tạo mới Student với Postman

    API lấy danh sách Student:

    Endpoint: /api/students, method GET.

    API lấy danh sách Student với Postman

4. Đóng gói container với pods và triển khai trên Kubernetes

Ở bước 3 chúng ta đã đóng gói ứng dụng Spring boot với container, trong phần này thì chúng ta sẽ tìm hiểu cách đóng gói containers với pod trong Kubernetes.

pods kubernetes

Pod là gì?

Đối với Docker, containers là đơn vị nhỏ nhất có thể triển khai, còn với Kubernetes đó là pod. Kubernetes không chạy container một cách trực tiếp mà thay vào đó Kubernetes sẽ đóng gói một hoặc nhiều containers trong một pod và triển khai pod trên cụm Kubernetes. Các containers bên trong pod chia sẻ network namespace bao gồm địa chỉ IP, network port và storage resources:

  • Network: Các containers bên trong pod có thể giao tiếp với nhau dễ dàng thông qua localhost:<PORT_CONTAINER>.
  • Storage: Các containers bên trong pod có thể chia sẻ với nhau một volume.

Tại sao Kubernetes sử dụng Pod mà không chạy trực tiếp containers?

Vì một pod có thể chứa một hoặc nhiều containers. Lý do chính để pod có thể chạy containers là để có thể chạy các ứng dụng trợ giúp hỗ trợ ứng dụng chính. Ví dụ như các ứng dụng đẩy và kéo dữ liệu, proxy, monitoring,.... Việc sử dụng một pod để chạy các containers này giúp đơn giản hoá quá trình giao tiếp, làm việc giữa các ứng dụng này. Tham khảo thêm trang web chính thức của Kubernetes

Định nghĩa Pod YAML file

Pod hoặc các object khác của Kubernetes thường được tạo bằng cách định nghĩa các file YAML (thường được gọi là file YAML manifest). Bạn cũng có thể sử dụng lệnh kubectl run ... để tạo các object nhưng trong thực tế, cách này không thường xuyên được sử dụng. Lý do là việc tạo các object thông qua định nghĩa các file YAML giúp chúng ta tái sử dụng nhiều lần, bảo đảm tính nhất quá và quản lý một cách tốt hơn. Ví dụ như file YAML dưới đây định nghĩa một pod trên k8s.

pod-springboot-series.yaml
apiVersion: v1
kind: Pod
metadata:
  name: k8s-springboot-series
  labels:
    app: k8s-springboot-series
spec:
  containers:
    - name: k8s-springboot-series
      image: thanhnb1/k8s-springboot-series:latest
      ports:
        - containerPort: 8080
          name: http
  restartPolicy: Always

Mô tả các phần của file yaml trên:

TênĐịnh nghĩa
apiVersionVersion của Kubernetes API mà bạn sử dụng để tạo object/resource, ở đây là v1.
kindLoại object/resource của Kubernetes, ở file trên là pod.
metadataCác thông tin như: name, labels, namespace và các thông tin khác của object/resource. Ở file trên, tên của pod là: name: k8s-springboot-series, lables của pod là: app: k8s-springboot-series.
specMô tả các thông tin của container như: tên container, docker image được sử dụng, port của container. Ở file trên, tên của container là: name: k8s-springboot-series, dùng image: image: thanhnb1/k8s-springboot-series:latest, port của container là: containerPort: 8080.

Tạo pod từ YAML file

Để tạo pod từ YAML file ta sử dụng lệnh: kubectl apply -f <path-yaml-file>. Lệnh này còn được sử dụng để tạo các object/resource khác của Kubernetes, không chi riêng pod.

kubectl apply -f <path-yaml-file>

# Tạo Pod từ file pod-springboot-series.yaml
kubectl apply -f k8s/pod-springboot-series.yaml
pod/k8s-springboot-series created

Lấy danh sách pods

Sau khi tạo pod từ YAML file thành công, nếu muốn lấy danh sách pod đã tạo ta sử dụng lệnh: kubectl get pods. Nếu trạng thái của pod là Running thì quá trình tạo pod đã thành công.

kubectl get pods

NAME                               READY   STATUS    RESTARTS        AGE
k8s-springboot-series              1/1     Running   0               3m30s

Xem logs của ứng dụng

Trong series này, ứng dụng của chúng ta sử dụng spring boot, để xem logs của ứng dụng bên trong pod ta sử dụng lệnh: kubectl logs -f pod/<pod-name>. Trong trường hợp pod của các bạn có nhiều hơn một container thì có thể thêm tham số -c như sau: kubectl logs -f pod/<pod-name> -c <container-name> để xem logs của container cụ thể.

kubectl logs -f pod/k8s-springboot-series

kubectl logs -f pod/k8s-springboot-series -c k8s-springboot-series

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.6)

2022-12-11 23:23:53.775  INFO 1 --- [           main] c.v.k.K8sSpringbootSeriesApplication     : Starting K8sSpringbootSeriesApplication v0.0.1-SNAPSHOT using Java 11.0.16.1 on k8s-springboot-series with PID 1 (/workspace/app.jar started by root in /workspace)
2022-12-11 23:23:53.776  INFO 1 --- [           main] c.v.k.K8sSpringbootSeriesApplication     : No active profile set, falling back to 1 default profile: "default"
2022-12-11 23:23:54.309  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-12-11 23:23:54.316  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-12-11 23:23:54.316  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.69]
2022-12-11 23:23:54.355  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-12-11 23:23:54.356  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 553 ms
2022-12-11 23:23:54.660  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2022-12-11 23:23:54.666  INFO 1 --- [           main] c.v.k.K8sSpringbootSeriesApplication     : Started K8sSpringbootSeriesApplication in 1.14 seconds (JVM running for 1.384)

Gửi request đến ứng dụng bên trong Pod

Trạng thái của pod ở phần trên là Running. Khi xem logs của ứng dụng thì thấy dòng Tomcat started on port(s): 8080, điều đó có nghĩa là ứng dụng spring boot đã được khởi động và đang lắng nghe ở cổng 8080. Trong thực tế, chúng ta sẽ không serve các request trực tiếp bằng pod mà sẽ sử dụng Service. Service là một cách để expose một ứng dụng chạy trên nhiều pods như một dịch vụ mạng (network service). Tuy nhiên chúng ta sẽ tìm hiểu sau hơn về nó ở bài viết sau, trong bài viết này, chúng ta sẽ kiểm tra ứng dụng bằng cách gửi request trực tiếp đến pod theo 2 cách:

  1. Gửi request từ bên ngoài

Sử dụng lệnh kubectl port-forward để forward port 8888 của máy local đến port 8080 của ứng dụng

kubectl port-forward pod/k8s-springboot-series 8888:8080

Sau khi chạy lệnh trên, chúng ta có thể truy cập ứng dụng và thông qua port 8888. Dùng Postman gọi các API:

API tạo mới Student

call-api-create-student-postman

API danh sách Student

call-api-get-all-student-postman

  1. Gửi request từ một pod khác

Tạo mới một debug-pod và gọi đến API của pod k8s-springboot-series từ pod mới được tạo thông qua IP.

debug-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: debug-pod
spec:
  containers:
    - image: pete911/debug-pod:latest
      name: debug-pod
      command:
        - sleep
        - "36000"

Tạo debug-pod từ yaml file trên:

kubectl apply -f k8s/pod-test-api.yaml

Lấy danh sách pods cùng với IP:

kubectl get pods -o wide
NAME                               READY   STATUS    RESTARTS         AGE     IP            NODE   NOMINATED NODE   READINESS GATES
k8s-springboot-series              1/1     Running   0                99m     10.1.28.73    nbt    <none>           <none>
debug-pod                          1/1     Running   0                9m15s   10.1.28.75    nbt    <none>           <none>

Truy cập vào pod: debug-pod và gọi api đến pod: k8s-springboot-series thông qua IP: 10.1.28.73:

kubectl exec -it po/debug-pod sh

# Gọi đến API lấy danh sách Student

/ # curl --location --request GET '10.1.28.73:8080/api/students'
[{"id":"90b1115a-66b4-48c3-bbb6-6a20cd2c5edc","fullName":"NGUYEN BA THANH","dateOfBirth":"29/04/1998","hometown":"HA DONG, HA NOI","gender":"MALE"}]/ #

Như vậy là chúng ta đã gọi API thành công từ debug-pod tới pod k8s-springboot-series thông qua IP: 10.1.28.73. Địa chỉ IP (IP: 10.1.28.73)được gán cho pod khi nó được tạo mới, điều này có nghĩa là mỗi lần pod bị xóa đi tạo lại, nó sẽ được gán cho một IP mới. IP này chỉ sử dụng được trong nội bộ cluster, không truy cập được IP này từ bên ngoài Cluster.

5. Tổng kết