0
0
Lập trình
NM

Kết hợp Bazel với Docker: Hướng dẫn chi tiết

Đăng vào 1 ngày trước

• 7 phút đọc

Hướng Dẫn Kết Hợp Bazel Với Docker

Chào mừng bạn đến với pikoTutorial tiếp theo! Trong một bài viết gần đây, tôi đã hướng dẫn cách xây dựng và chạy các container Docker bằng cách sử dụng CMake. Hôm nay, chúng ta sẽ xem xét cách thực hiện điều tương tự nhưng bằng cách sử dụng Bazel. Dưới đây là cấu trúc dự án của chúng ta:

Copy
project/
├── app1/
│   ├── BUILD
│   ├── main.py
│   ├── run_container.sh
│   ├── some_config.json
├── app2/
│   ├── BUILD
│   ├── main.py
│   ├── run_container.sh
│   ├── some_config.json
├── MODULE.bazel
└── WORKSPACE

Lưu ý: Ngược lại với cấu trúc dự án được sử dụng trong bài viết về CMake, cây thư mục này không chứa bất kỳ Dockerfile nào. Điều này là do Bazel hỗ trợ xây dựng các hình ảnh tuân thủ OCI, độc lập với công cụ cụ thể nào (như Docker). Tuy nhiên, ở bước cuối cùng, tôi sẽ sử dụng Docker để chạy container.

Tệp MODULE.bazel

Đầu tiên, chúng ta cần xác định các phụ thuộc của quá trình xây dựng. Chúng chứa các quy tắc mà tôi sẽ sử dụng trong các tệp Bazel:

Copy
# phụ thuộc cần thiết để tạo một nhị phân python và một lớp ứng dụng python trong hình ảnh
bazel_dep(name = "aspect_rules_py", version = "1.4.0")
# phụ thuộc cần thiết để xây dựng hình ảnh OCI
bazel_dep(name = "rules_oci", version = "2.2.6")
# phụ thuộc cần thiết cho quy tắc pkg_tar mà chúng ta sẽ sử dụng để tạo
# một lớp hình ảnh bổ sung
bazel_dep(name = "rules_pkg", version = "1.1.0")

Tệp WORKSPACE

Tiếp theo là tệp WORKSPACE. Một số thao tác (như kéo một hình ảnh cơ sở) chỉ có thể được thực hiện trong giai đoạn tải không gian làm việc, vì vậy chúng phải có trong tệp WORKSPACE.

Copy
load("@rules_oci//oci:dependencies.bzl", "rules_oci_dependencies")
rules_oci_dependencies()

load("@rules_oci//oci:repositories.bzl", "oci_register_toolchains")
oci_register_toolchains(name = "oci")

load("@rules_oci//oci:pull.bzl", "oci_pull")
oci_pull(
    name = "python_base",
    image = "python",
    tag = "3.9-slim",
    platforms = ["linux/amd64"],
)

Trong bài viết này, có hai ứng dụng Python, vì vậy tôi kéo một hình ảnh cơ sở chuyên dụng cho Python, nhưng nếu bạn sử dụng ngôn ngữ khác, đây là nơi bạn chỉ định hình ảnh cơ sở phù hợp.

Các tệp app1/main.py và app2/main.py

Không có gì đặc biệt ở đây, chỉ là một số mã Python in nội dung của tệp cấu hình để kiểm tra xem container hoạt động hay không:

Copy
import json

with open("some_config.json", "r") as file:
    print(f"Configuration from app 1: {json.load(file)}")

Tệp some_config.json là một từ điển với một khóa:

Copy
{
    "Hello": "World"
}

Tương tự, nội dung của app2/some_config.json là:

Copy
{
    "Bye": "World"
}

Các tệp BUILD

Cả hai tệp app1/BUILDapp2/BUILD có cấu trúc tương tự với điểm khác biệt duy nhất là tên ứng dụng:

Copy
load("@aspect_rules_py//py:defs.bzl", "py_binary", "py_image_layer")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_load")
load("@rules_pkg//:pkg.bzl", "pkg_tar")
# tạo một nhị phân ứng dụng Python
py_binary(
    name = "main",
    srcs = ["main.py"],
)
# tạo một lớp ứng dụng Python trong hình ảnh
py_image_layer(
    name = "application_layer",
    binary = ":main",
)
# tạo một lớp bổ sung trong hình ảnh
pkg_tar(
    name = "configuration_layer",
    srcs = ["some_config.json"],
)
# tạo hình ảnh OCI
oci_image(
    name = "image_definition",
    base = "@python_base",
    entrypoint = ["/app1/main"],
    tars = [":configuration_layer", ":application_layer"],
)
# tải hình ảnh vào runtime cục bộ
oci_load(
    name = "image",
    image = ":image_definition",
    repo_tags = ["image_app_1:latest"],
)

Với tệp BUILD như vậy, tôi đã định nghĩa một hình ảnh OCI hai lớp mẫu có thể được tải vào runtime cục bộ (và có thể truy cập ví dụ với Docker).

Xây dựng các hình ảnh

Bây giờ khi mọi thứ đã sẵn sàng, bạn có thể xây dựng hình ảnh app1 bằng cách gọi:

Copy
bazel run //app1:image

Điều này sẽ tạo ra đầu ra sau:

Copy
INFO: Analyzed target //app1:image (152 packages loaded, 3970 targets configured).
INFO: Found 1 target...
Target //app1:image up-to-date:
  bazel-bin/app1/image.sh
INFO: Elapsed time: 5.106s, Critical Path: 4.20s
INFO: 40 processes: 25 internal, 14 linux-sandbox, 1 local.
INFO: Build completed successfully, 40 total actions
INFO: Running command line: bazel-bin/app1/image.sh
6c4c763d22d0: Loading layer [==================================================>]  28.23MB/28.23MB
41757dc445c9: Loading layer [==================================================>]  3.512MB/3.512MB
529e75018436: Loading layer [==================================================>]  14.93MB/14.93MB
678221e973fe: Loading layer [==================================================>]     249B/249B
c025797afb0a: Loading layer [==================================================>]  10.24kB/10.24kB
7e7cd3ed4a69: Loading layer [==================================================>]  2.483MB/2.483MB
f465bed610cd: Loading layer [==================================================>]  23.19MB/23.19MB
7141b744acb3: Loading layer [==================================================>]  1.103MB/1.103MB
Loaded image: image_app_1:latest

Và tương tự cho hình ảnh app2:

Copy
bazel run //app2:image

Lưu ý cho người mới bắt đầu: hãy chú ý rằng quy tắc oci_load mà tôi đã sử dụng để định nghĩa các mục tiêu //app1:image//app2:image phải được gọi bằng bazel run. Gọi bazel build trên các mục tiêu này sẽ không tải hình ảnh vào runtime cục bộ.

Sau khi quá trình xây dựng hoàn tất, bạn có thể chạy:

Copy
docker images

để kiểm tra rằng 2 hình ảnh mới đã xuất hiện:

Copy
REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
image_app_1   latest    95ef84a311e5   2 minutes ago   214MB
image_app_2   latest    8d757bc74a7e   2 minutes ago   214MB

Bạn có thể chạy một trong số chúng bằng cách gọi, ví dụ:

Copy
docker run --rm --name container_app_1 image_app_1

Và bạn sẽ thấy rằng không chỉ ứng dụng của chúng ta hoạt động trong container, mà nó còn có quyền truy cập vào tệp cấu hình đã được thêm vào hình ảnh của chúng ta trong lớp cấu hình:

Copy
Configuration from app 1: {'Hello': 'World'}

Chạy Container Docker với Bazel

Nhưng tại sao lại dừng lại ở đây? Chúng ta có một lớp trừu tượng đẹp cho việc tạo hình ảnh (dưới dạng mục tiêu Bazel //app1:image), vì vậy hãy thêm một lớp trừu tượng tương tự cho việc chạy container từ cấp độ Bazel như thế này:

Copy
bazel run //app1:container

Để thực hiện điều đó, cần có một kịch bản bọc nào đó sẽ được sử dụng trong tệp BUILD của Bazel, hãy gọi nó là run_container.sh:

Copy
#!/bin/bash

docker run --rm --name container_app_1 image_app_1

Sau đó, nó có thể được sử dụng trong quy tắc sh_binary trong tệp app1/BUILD:

Copy
sh_binary(
    name = "container",
    srcs = ["run_container.sh"],
    data = [":image"],
)

Bây giờ, khi bạn gọi:

Copy
bazel run //app1:container

Bazel sẽ xử lý việc chạy container đã chỉ định:

Copy
INFO: Analyzed target //app1:container (0 packages loaded, 24 targets configured).
INFO: Found 1 target...
Target //app1:container up-to-date:
  bazel-bin/app1/container
INFO: Elapsed time: 0.275s, Critical Path: 0.09s
INFO: 5 processes: 5 internal.
INFO: Build completed successfully, 5 total actions
INFO: Running command line: bazel-bin/app1/container
Configuration from app 1: {'Hello': 'World'}

Lưu ý cho người mới bắt đầu: mục tiêu //app1:container sẽ không phát hiện bất kỳ thay đổi nào được thực hiện trong hình ảnh (ví dụ: trong mã ứng dụng), vì vậy với thiết lập như vậy, nếu bạn muốn chạy phiên bản mới nhất của hình ảnh, bạn phải đảm bảo rằng bazel run //app1:image đã được gọi trước khi chạy bazel run //app1:container.

Các Thực Hành Tốt Nhất

  • Tổ chức mã: Hãy chắc chắn rằng mã của bạn được tổ chức tốt để dễ duy trì và mở rộng.
  • Sử dụng phiên bản mới nhất: Luôn cập nhật các phụ thuộc của bạn để tận dụng các tính năng và cải tiến hiệu suất mới nhất.
  • Kiểm tra kỹ lưỡng: Thực hiện kiểm tra với từng container để đảm bảo rằng mọi thứ hoạt động như mong đợi.

Các Cạm Bẫy Thường Gặp

  • Bỏ qua phụ thuộc: Đảm bảo rằng tất cả các phụ thuộc cần thiết đều được xác định trong tệp MODULE.bazel.
  • Gọi sai quy tắc: Đảm bảo gọi đúng các quy tắc của Bazel để tránh lỗi không mong muốn.

Mẹo Tối Ưu Hiệu Suất

  • Sử dụng caching: Bazel hỗ trợ caching, giúp tăng tốc độ xây dựng cho các lần chạy sau.
  • Tối ưu hóa hình ảnh: Giảm kích thước hình ảnh bằng cách loại bỏ các tệp không cần thiết.

Giải Quyết Sự Cố

  • Lỗi không tìm thấy hình ảnh: Kiểm tra xem hình ảnh đã được tải thành công vào runtime chưa.
  • Lỗi trong mã ứng dụng: Sử dụng logging để kiểm tra các lỗi trong mã ứng dụng.

Câu Hỏi Thường Gặp (FAQ)

1. Bazel là gì?

Bazel là một công cụ xây dựng mạnh mẽ hỗ trợ xây dựng và kiểm thử phần mềm.

2. Tại sao sử dụng Bazel với Docker?

Việc kết hợp Bazel và Docker giúp tối ưu hóa quy trình xây dựng và triển khai ứng dụng.

3. Có thể sử dụng Bazel cho ngôn ngữ khác không?

Có, Bazel hỗ trợ nhiều ngôn ngữ lập trình khác nhau.

Hy vọng rằng bài viết này sẽ giúp bạn hiểu rõ hơn về cách kết hợp Bazel với Docker. Nếu bạn có bất kỳ câu hỏi nào, đừng ngần ngại để lại ý kiến dưới bài viết này!

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào