Thử các công cụ mới để xây dựng và tự động triển khai trong Kubernetes

Thử các công cụ mới để xây dựng và tự động triển khai trong Kubernetes

Xin chào! Gần đây, nhiều công cụ tự động hóa thú vị đã được phát hành để xây dựng Docker image và triển khai cho Kubernetes. Về vấn đề này, tôi quyết định thử nghiệm GitLab, nghiên cứu kỹ lưỡng các khả năng của nó và tất nhiên là thiết lập quy trình.

Tác phẩm này được lấy cảm hứng từ trang web kubernetes.io, được tạo ra từ mã nguồn tự động và đối với mỗi yêu cầu nhóm được gửi, robot sẽ tự động tạo phiên bản xem trước của trang web với các thay đổi của bạn và cung cấp liên kết để xem.

Tôi đã cố gắng xây dựng một quy trình tương tự từ đầu nhưng được xây dựng hoàn toàn trên Gitlab CI và các công cụ miễn phí mà tôi thường sử dụng để triển khai ứng dụng lên Kubernetes. Hôm nay cuối cùng tôi sẽ cho bạn biết thêm về họ.

Bài viết sẽ thảo luận về các công cụ như:
Hugo, qbec, kaniko, git-mật mã и CI GitLab với việc tạo ra các môi trường năng động.

Nội dung

  1. Gặp gỡ Hugo
  2. Chuẩn bị Dockerfile
  3. Làm quen với Kaniko
  4. Làm quen với qbec
  5. Dùng thử Gitlab-runner với Kubernetes-executor
  6. Triển khai biểu đồ Helm với qbec
  7. Giới thiệu git-crypt
  8. Tạo hình ảnh hộp công cụ
  9. Quy trình đầu tiên của chúng tôi và tập hợp hình ảnh theo thẻ
  10. Tự động hóa triển khai
  11. Hiện vật và lắp ráp khi đẩy lên làm chủ
  12. Môi trường năng động
  13. Đánh giá ứng dụng

1. Làm quen với Hugo

Để làm ví dụ cho dự án của chúng tôi, chúng tôi sẽ cố gắng tạo một trang xuất bản tài liệu được xây dựng trên Hugo. Hugo là một trình tạo nội dung tĩnh.

Đối với những người không quen thuộc với máy tạo tĩnh, tôi sẽ cho bạn biết thêm một chút về chúng. Không giống như các công cụ trang web thông thường có cơ sở dữ liệu và một số PHP, khi người dùng yêu cầu sẽ tạo các trang một cách nhanh chóng, các trình tạo tĩnh được thiết kế hơi khác một chút. Chúng cho phép bạn lấy nguồn, thường là một tập hợp các tệp trong các mẫu chủ đề và đánh dấu Markdown, sau đó biên dịch chúng thành một trang web hoàn chỉnh.

Kết quả là, bạn sẽ nhận được cấu trúc thư mục và một tập hợp các tệp HTML được tạo mà bạn có thể chỉ cần tải lên bất kỳ dịch vụ lưu trữ giá rẻ nào và có được một trang web hoạt động.

Bạn có thể cài đặt Hugo cục bộ và dùng thử:

Khởi tạo một trang web mới:

hugo new site docs.example.org

Và đồng thời kho git:

cd docs.example.org
git init

Cho đến nay, trang web của chúng tôi vẫn còn nguyên sơ và để có thứ gì đó xuất hiện trên đó, trước tiên chúng tôi cần kết nối một chủ đề; chủ đề chỉ là một tập hợp các mẫu và quy tắc cụ thể mà trang web của chúng tôi được tạo ra.

Đối với chủ đề chúng tôi sẽ sử dụng Tìm hiểu, theo ý kiến ​​​​của tôi, hoàn toàn phù hợp cho một trang web tài liệu.

Tôi đặc biệt chú ý đến thực tế là chúng ta không cần lưu các tệp chủ đề trong kho dự án của mình; thay vào đó, chúng ta có thể chỉ cần kết nối nó bằng cách sử dụng mô hình con git:

git submodule add https://github.com/matcornic/hugo-theme-learn themes/learn

Do đó, kho lưu trữ của chúng tôi sẽ chỉ chứa các tệp liên quan trực tiếp đến dự án của chúng tôi và chủ đề được kết nối sẽ vẫn là một liên kết đến một kho lưu trữ cụ thể và một cam kết trong đó, nghĩa là nó luôn có thể được lấy từ nguồn ban đầu và không sợ bị khóa. những thay đổi không tương thích.

Hãy sửa cấu hình config.toml:

baseURL = "http://docs.example.org/"
languageCode = "en-us"
title = "My Docs Site"
theme = "learn"

Ở giai đoạn này bạn có thể chạy:

hugo server

Và tại địa chỉ http://localhost:1313/ kiểm tra trang web mới tạo của chúng tôi, mọi thay đổi được thực hiện trong thư mục đều tự động cập nhật trang đang mở trên trình duyệt, rất tiện lợi!

Hãy thử tạo một trang bìa trong nội dung/_index.md:

# My docs site

## Welcome to the docs!

You will be very smart :-)

Ảnh chụp màn hình của trang mới được tạo

Thử các công cụ mới để xây dựng và tự động triển khai trong Kubernetes

Để tạo một trang web, chỉ cần chạy:

hugo

Nội dung thư mục công cộng/ và sẽ là trang web của bạn.
Vâng, nhân tiện, hãy thêm nó ngay vào .gitignore:

echo /public > .gitignore

Đừng quên cam kết những thay đổi của chúng tôi:

git add .
git commit -m "New site created"

2. Chuẩn bị Dockerfile

Đã đến lúc xác định cấu trúc kho lưu trữ của chúng ta. Tôi thường sử dụng một cái gì đó như:

.
├── deploy
│   ├── app1
│   └── app2
└── dockerfiles
    ├── image1
    └── image2

  • dockerfiles/ — chứa các thư mục chứa Dockerfiles và mọi thứ cần thiết để xây dựng hình ảnh Docker của chúng tôi.
  • triển khai/ — chứa các thư mục để triển khai ứng dụng của chúng tôi lên Kubernetes

Vì vậy, chúng ta sẽ tạo Dockerfile đầu tiên dọc theo đường dẫn dockerfiles/trang web/Dockerfile

FROM alpine:3.11 as builder
ARG HUGO_VERSION=0.62.0
RUN wget -O- https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_linux-64bit.tar.gz | tar -xz -C /usr/local/bin
ADD . /src
RUN hugo -s /src

FROM alpine:3.11
RUN apk add --no-cache darkhttpd
COPY --from=builder /src/public /var/www
ENTRYPOINT [ "/usr/bin/darkhttpd" ]
CMD [ "/var/www" ]

Như bạn có thể thấy, Dockerfile chứa hai TỪ, tính năng này được gọi là xây dựng nhiều giai đoạn và cho phép bạn loại trừ mọi thứ không cần thiết khỏi hình ảnh Docker cuối cùng.
Vì vậy, hình ảnh cuối cùng sẽ chỉ chứa darkhttpd (máy chủ HTTP nhẹ) và công cộng/ — nội dung của trang web được tạo tĩnh của chúng tôi.

Đừng quên cam kết những thay đổi của chúng tôi:

git add dockerfiles/website
git commit -m "Add Dockerfile for website"

3. Làm quen với Kaniko

Là người xây dựng hình ảnh docker, tôi quyết định sử dụng kaniko, vì hoạt động của nó không yêu cầu trình nền docker và bản thân quá trình xây dựng có thể được thực hiện trên bất kỳ máy nào và bộ đệm có thể được lưu trữ trực tiếp trong sổ đăng ký, do đó loại bỏ nhu cầu phải có bộ lưu trữ liên tục đầy đủ.

Để xây dựng hình ảnh, chỉ cần chạy container với người thi hành án kaniko và chuyển nó vào bối cảnh xây dựng hiện tại; điều này cũng có thể được thực hiện cục bộ, thông qua docker:

docker run -ti --rm 
  -v $PWD:/workspace 
  -v ~/.docker/config.json:/kaniko/.docker/config.json:ro 
  gcr.io/kaniko-project/executor:v0.15.0 
  --cache 
  --dockerfile=dockerfiles/website/Dockerfile 
  --destination=registry.gitlab.com/kvaps/docs.example.org/website:v0.0.1

Где register.gitlab.com/kvaps/docs.example.org/website — tên của hình ảnh docker của bạn; sau khi xây dựng, nó sẽ tự động được khởi chạy vào sổ đăng ký docker.

Thông số --bộ nhớ đệm cho phép bạn lưu trữ các lớp trong sổ đăng ký docker; ví dụ đã cho, chúng sẽ được lưu trong register.gitlab.com/kvaps/docs.example.org/website/cache, nhưng bạn có thể chỉ định một đường dẫn khác bằng tham số --cache-repo.

Ảnh chụp màn hình của docker-registry

Thử các công cụ mới để xây dựng và tự động triển khai trong Kubernetes

4. Làm quen với qbec

Qbec là một công cụ triển khai cho phép bạn mô tả khai báo các bảng kê khai ứng dụng của mình và triển khai chúng lên Kubernetes. Sử dụng Jsonnet làm cú pháp chính cho phép bạn đơn giản hóa đáng kể việc mô tả sự khác biệt trên nhiều môi trường và cũng loại bỏ gần như hoàn toàn việc lặp lại mã.

Điều này có thể đặc biệt đúng trong trường hợp bạn cần triển khai một ứng dụng lên một số cụm với các tham số khác nhau và muốn mô tả chúng một cách khai báo trong Git.

Qbec cũng cho phép bạn hiển thị biểu đồ Helm bằng cách chuyển cho chúng các tham số cần thiết và sau đó vận hành chúng theo cách tương tự như các bảng kê khai thông thường, bao gồm cả việc bạn có thể áp dụng các đột biến khác nhau cho chúng và điều này, đến lượt nó, cho phép bạn loại bỏ nhu cầu sử dụng ChartMuseum. Nghĩa là, bạn có thể lưu trữ và hiển thị biểu đồ trực tiếp từ git, nơi chúng thuộc về.

Như tôi đã nói trước đó, chúng tôi sẽ lưu trữ tất cả các triển khai trong một thư mục triển khai/:

mkdir deploy
cd deploy

Hãy khởi tạo ứng dụng đầu tiên của chúng ta:

qbec init website
cd website

Bây giờ cấu trúc ứng dụng của chúng ta trông như thế này:

.
├── components
├── environments
│   ├── base.libsonnet
│   └── default.libsonnet
├── params.libsonnet
└── qbec.yaml

chúng ta hãy nhìn vào tập tin qbec.yaml:

apiVersion: qbec.io/v1alpha1
kind: App
metadata:
  name: website
spec:
  environments:
    default:
      defaultNamespace: docs
      server: https://kubernetes.example.org:8443
  vars: {}

Ở đây chúng tôi chủ yếu quan tâm đến thông số kỹ thuật, qbec đã tạo môi trường mặc định cho chúng tôi và lấy địa chỉ máy chủ cũng như vùng tên từ kubeconfig hiện tại của chúng tôi.
Bây giờ khi triển khai tới mặc định môi trường, qbec sẽ luôn chỉ triển khai đến cụm Kubernetes được chỉ định và vào không gian tên được chỉ định, nghĩa là bạn không còn phải chuyển đổi giữa ngữ cảnh và không gian tên để thực hiện triển khai.
Nếu cần, bạn luôn có thể cập nhật cài đặt trong tệp này.

Tất cả môi trường của bạn được mô tả trong qbec.yaml, và trong tập tin thông số.libsonnet, nơi nó cho biết nơi lấy thông số cho chúng.

Tiếp theo chúng ta thấy hai thư mục:

  • các thành phần / — tất cả các bảng kê khai cho ứng dụng của chúng ta sẽ được lưu trữ ở đây; chúng có thể được mô tả cả trong tệp jsonnet và tệp yaml thông thường
  • môi trường / — ở đây chúng tôi sẽ mô tả tất cả các biến (tham số) cho môi trường của chúng tôi.

Theo mặc định, chúng tôi có hai tệp:

  • môi trường/base.libsonnet - nó sẽ chứa các tham số chung cho tất cả các môi trường
  • môi trường/default.libsonnet - chứa các tham số được ghi đè cho môi trường mặc định

hãy mở ra môi trường/base.libsonnet và thêm tham số cho thành phần đầu tiên của chúng tôi tại đó:

{
  components: {
    website: {
      name: 'example-docs',
      image: 'registry.gitlab.com/kvaps/docs.example.org/website:v0.0.1',
      replicas: 1,
      containerPort: 80,
      servicePort: 80,
      nodeSelector: {},
      tolerations: [],
      ingressClass: 'nginx',
      domain: 'docs.example.org',
    },
  },
}

Chúng ta hãy tạo thành phần đầu tiên của chúng ta thành phần/website.jsonnet:

local env = {
  name: std.extVar('qbec.io/env'),
  namespace: std.extVar('qbec.io/defaultNs'),
};
local p = import '../params.libsonnet';
local params = p.components.website;

[
  {
    apiVersion: 'apps/v1',
    kind: 'Deployment',
    metadata: {
      labels: { app: params.name },
      name: params.name,
    },
    spec: {
      replicas: params.replicas,
      selector: {
        matchLabels: {
          app: params.name,
        },
      },
      template: {
        metadata: {
          labels: { app: params.name },
        },
        spec: {
          containers: [
            {
              name: 'darkhttpd',
              image: params.image,
              ports: [
                {
                  containerPort: params.containerPort,
                },
              ],
            },
          ],
          nodeSelector: params.nodeSelector,
          tolerations: params.tolerations,
          imagePullSecrets: [{ name: 'regsecret' }],
        },
      },
    },
  },
  {
    apiVersion: 'v1',
    kind: 'Service',
    metadata: {
      labels: { app: params.name },
      name: params.name,
    },
    spec: {
      selector: {
        app: params.name,
      },
      ports: [
        {
          port: params.servicePort,
          targetPort: params.containerPort,
        },
      ],
    },
  },
  {
    apiVersion: 'extensions/v1beta1',
    kind: 'Ingress',
    metadata: {
      annotations: {
        'kubernetes.io/ingress.class': params.ingressClass,
      },
      labels: { app: params.name },
      name: params.name,
    },
    spec: {
      rules: [
        {
          host: params.domain,
          http: {
            paths: [
              {
                backend: {
                  serviceName: params.name,
                  servicePort: params.servicePort,
                },
              },
            ],
          },
        },
      ],
    },
  },
]

Trong tệp này, chúng tôi đã mô tả ba thực thể Kubernetes cùng một lúc, đó là: Triển khai, Dịch vụ и Sự đi vào. Nếu muốn, chúng ta có thể đặt chúng vào các thành phần khác nhau, nhưng ở giai đoạn này, chỉ một thành phần là đủ đối với chúng ta.

cú pháp jsonnet rất giống với json thông thường, về nguyên tắc, json thông thường đã là jsonnet hợp lệ, vì vậy ban đầu bạn có thể dễ dàng sử dụng các dịch vụ trực tuyến như yaml2json để chuyển đổi yaml thông thường của bạn thành json hoặc nếu các thành phần của bạn không chứa bất kỳ biến nào thì chúng có thể được mô tả dưới dạng yaml thông thường.

Khi làm việc với jsonnet Tôi thực sự khuyên bạn nên cài đặt plugin cho trình chỉnh sửa của mình

Ví dụ: có một plugin cho vim vim-jsonnet, bật tính năng tô sáng cú pháp và tự động thực thi jsonnet fmt mỗi khi bạn lưu (yêu cầu cài đặt jsonnet).

Mọi thứ đã sẵn sàng, bây giờ chúng ta có thể bắt đầu triển khai:

Để xem những gì chúng tôi có, hãy chạy:

qbec show default

Ở đầu ra, bạn sẽ thấy các tệp kê khai yaml được hiển thị sẽ được áp dụng cho cụm mặc định.

Tuyệt vời, bây giờ hãy áp dụng:

qbec apply default

Ở đầu ra, bạn sẽ luôn thấy những gì sẽ được thực hiện trong cụm của mình, qbec sẽ yêu cầu bạn đồng ý với những thay đổi bằng cách nhập y bạn sẽ có thể xác nhận ý định của bạn.

Ứng dụng của chúng tôi đã sẵn sàng và được triển khai!

Nếu bạn thực hiện thay đổi, bạn luôn có thể thực hiện:

qbec diff default

để xem những thay đổi này sẽ ảnh hưởng như thế nào đến việc triển khai hiện tại

Đừng quên cam kết những thay đổi của chúng tôi:

cd ../..
git add deploy/website
git commit -m "Add deploy for website"

5. Dùng thử Gitlab-runner với Kubernetes-executor

Cho đến gần đây tôi chỉ sử dụng thường xuyên Á hậu gitlab trên máy được chuẩn bị trước (thùng chứa LXC) có shell hoặc docker-executor. Ban đầu, chúng tôi có một số trình chạy như vậy được xác định trên toàn cầu trong gitlab của chúng tôi. Họ đã thu thập hình ảnh docker cho tất cả các dự án.

Nhưng thực tế đã chỉ ra, phương án này không phải là lý tưởng nhất, cả về tính thực dụng và an toàn. Sẽ tốt hơn và đúng đắn hơn về mặt tư tưởng nếu triển khai các trình chạy riêng biệt cho từng dự án hoặc thậm chí cho từng môi trường.

May mắn thay, đây không phải là vấn đề gì cả, vì bây giờ chúng tôi sẽ triển khai Á hậu gitlab trực tiếp như một phần dự án của chúng tôi ngay trong Kubernetes.

Gitlab cung cấp biểu đồ hỗ trợ được tạo sẵn để triển khai gitlab-runner cho Kubernetes. Vì vậy tất cả những gì bạn cần làm là tìm hiểu mã thông báo đăng ký cho dự án của chúng tôi ở Cài đặt -> CI/CD -> Người chạy và chuyển nó cho người lãnh đạo:

helm repo add gitlab https://charts.gitlab.io

helm install gitlab-runner 
  --set gitlabUrl=https://gitlab.com 
  --set runnerRegistrationToken=yga8y-jdCusVDn_t4Wxc 
  --set rbac.create=true 
  gitlab/gitlab-runner

Trong đó:

  • https://gitlab.com - địa chỉ máy chủ Gitlab của bạn.
  • yga8y-jdCusVDn_t4Wxc — mã thông báo đăng ký cho dự án của bạn.
  • rbac.create=true — cung cấp cho người chạy số lượng đặc quyền cần thiết để có thể tạo các nhóm thực hiện nhiệm vụ của chúng ta bằng cách sử dụng kubernetes-executor.

Nếu mọi thứ được thực hiện chính xác, bạn sẽ thấy một người chạy đã đăng ký trong phần Runners, trong cài đặt dự án của bạn.

Ảnh chụp màn hình của người chạy đã thêm

Thử các công cụ mới để xây dựng và tự động triển khai trong Kubernetes

Nó có đơn giản vậy không? - ừ, đơn giản thế thôi! Không còn rắc rối với việc đăng ký người chạy theo cách thủ công, từ giờ trở đi người chạy sẽ được tạo và hủy tự động.

6. Triển khai biểu đồ Helm với QBEC

Vì chúng tôi quyết định xem xét Á hậu gitlab một phần của dự án, đã đến lúc mô tả nó trong kho Git của chúng tôi.

Chúng ta có thể mô tả nó như một thành phần riêng biệt trang mạng, nhưng trong tương lai chúng tôi dự định triển khai các bản sao khác nhau trang mạng rất thường xuyên, không giống như Á hậu gitlab, sẽ chỉ được triển khai một lần trên mỗi cụm Kubernetes. Vì vậy hãy khởi tạo một ứng dụng riêng cho nó:

cd deploy
qbec init gitlab-runner
cd gitlab-runner

Lần này chúng tôi sẽ không mô tả các thực thể Kubernetes theo cách thủ công mà sẽ sử dụng biểu đồ Helm được tạo sẵn. Một trong những ưu điểm của qbec là khả năng hiển thị biểu đồ Helm trực tiếp từ kho Git.

Hãy kết nối nó bằng mô-đun con git:

git submodule add https://gitlab.com/gitlab-org/charts/gitlab-runner vendor/gitlab-runner

Bây giờ thư mục nhà cung cấp/gitlab-runner Chúng tôi có một kho lưu trữ có biểu đồ cho gitlab-runner.

Theo cách tương tự, bạn có thể kết nối các kho lưu trữ khác, chẳng hạn như toàn bộ kho lưu trữ với các biểu đồ chính thức https://github.com/helm/charts

Hãy mô tả thành phần thành phần/gitlab-runner.jsonnet:

local env = {
  name: std.extVar('qbec.io/env'),
  namespace: std.extVar('qbec.io/defaultNs'),
};
local p = import '../params.libsonnet';
local params = p.components.gitlabRunner;

std.native('expandHelmTemplate')(
  '../vendor/gitlab-runner',
  params.values,
  {
    nameTemplate: params.name,
    namespace: env.namespace,
    thisFile: std.thisFile,
    verbose: true,
  }
)

Đối số đầu tiên cho mở rộngMẫu mũ bảo hiểm chúng ta chuyển đường dẫn đến biểu đồ, sau đó thông số.giá trị, mà chúng tôi lấy từ các tham số môi trường, sau đó xuất hiện đối tượng với

  • tênMẫu - tiêu đề phát hành
  • không gian tên — không gian tên được chuyển sang vị trí lãnh đạo
  • tập tin này - một tham số bắt buộc chuyển đường dẫn đến tệp hiện tại
  • dài dòng - hiển thị lệnh mẫu mũ bảo hiểm với tất cả các đối số khi hiển thị biểu đồ

Bây giờ hãy mô tả các tham số cho thành phần của chúng ta trong môi trường/base.libsonnet:

local secrets = import '../secrets/base.libsonnet';

{
  components: {
    gitlabRunner: {
      name: 'gitlab-runner',
      values: {
        gitlabUrl: 'https://gitlab.com/',
        rbac: {
          create: true,
        },
        runnerRegistrationToken: secrets.runnerRegistrationToken,
      },
    },
  },
}

Chú ý Á hậuĐăng kýToken chúng tôi lấy từ một tập tin bên ngoài bí mật/base.libsonnet, hãy tạo nó:

{
  runnerRegistrationToken: 'yga8y-jdCusVDn_t4Wxc',
}

Hãy kiểm tra xem mọi thứ có hoạt động không:

qbec show default

nếu mọi thứ đều ổn thì chúng tôi có thể xóa bản phát hành đã triển khai trước đó của mình thông qua Helm:

helm uninstall gitlab-runner

và triển khai theo cách tương tự, nhưng thông qua qbec:

qbec apply default

7. Giới thiệu về git-crypt

Git-crypt là một công cụ cho phép bạn thiết lập mã hóa minh bạch cho kho lưu trữ của mình.

Hiện tại, cấu trúc thư mục của chúng tôi cho gitlab-runner trông như thế này:

.
├── components
│   ├── gitlab-runner.jsonnet
├── environments
│   ├── base.libsonnet
│   └── default.libsonnet
├── params.libsonnet
├── qbec.yaml
├── secrets
│   └── base.libsonnet
└── vendor
    └── gitlab-runner (submodule)

Nhưng lưu trữ bí mật trong Git không an toàn phải không? Vì vậy chúng ta cần mã hóa chúng đúng cách.

Thông thường, vì lợi ích của một biến, điều này không phải lúc nào cũng có ý nghĩa. Bạn có thể chuyển bí mật tới qbec và thông qua các biến môi trường của hệ thống CI của bạn.
Nhưng điều đáng chú ý là cũng có những dự án phức tạp hơn có thể chứa đựng nhiều bí mật hơn, việc chuyển tất cả chúng thông qua các biến môi trường sẽ vô cùng khó khăn.

Hơn nữa, trong trường hợp này tôi sẽ không thể nói cho bạn biết về một công cụ tuyệt vời như git-mật mã.

git-mật mã Nó cũng thuận tiện ở chỗ nó cho phép bạn lưu toàn bộ lịch sử bí mật, cũng như so sánh, hợp nhất và giải quyết xung đột giống như cách chúng ta thường làm trong trường hợp Git.

Điều đầu tiên sau khi cài đặt git-mật mã chúng ta cần tạo khóa cho kho lưu trữ của mình:

git crypt init

Nếu bạn có khóa PGP thì bạn có thể thêm chính mình làm cộng tác viên cho dự án này ngay lập tức:

git-crypt add-gpg-user [email protected]

Bằng cách này, bạn luôn có thể giải mã kho lưu trữ này bằng khóa riêng của mình.

Nếu bạn không có khóa PGP và không mong đợi nó, thì bạn có thể làm theo cách khác và xuất khóa dự án:

git crypt export-key /path/to/keyfile

Vì vậy, bất cứ ai có xuất khẩu tài liệu quan trọng sẽ có thể giải mã kho lưu trữ của bạn.

Đã đến lúc thiết lập bí mật đầu tiên của chúng ta.
Hãy để tôi nhắc bạn rằng chúng tôi vẫn đang ở trong thư mục triển khai/gitlab-runner/, nơi chúng tôi có một thư mục bí mật/, hãy mã hóa tất cả các tệp trong đó, vì điều này chúng tôi sẽ tạo một tệp bí mật/.gitattribut với nội dung sau:

* filter=git-crypt diff=git-crypt
.gitattributes !filter !diff

Như có thể thấy từ nội dung, tất cả các tệp đều bị che * sẽ được đưa qua git-mật mã, ngoại trừ hầu hết .gitattribut

Chúng ta có thể kiểm tra điều này bằng cách chạy:

git crypt status -e

Đầu ra sẽ là danh sách tất cả các tệp trong kho lưu trữ được bật mã hóa

Vậy là xong, bây giờ chúng ta có thể cam kết các thay đổi của mình một cách an toàn:

cd ../..
git add .
git commit -m "Add deploy for gitlab-runner"

Để chặn một kho lưu trữ, chỉ cần chạy:

git crypt lock

và ngay lập tức tất cả các tập tin được mã hóa sẽ biến thành thứ gì đó nhị phân, bạn sẽ không thể đọc được chúng.
Để giải mã kho lưu trữ, hãy chạy:

git crypt unlock

8. Tạo hình ảnh hộp công cụ

Hình ảnh hộp công cụ là hình ảnh có tất cả các công cụ mà chúng ta sẽ sử dụng để triển khai dự án của mình. Nó sẽ được người chạy Gitlab sử dụng để thực hiện các nhiệm vụ triển khai điển hình.

Mọi thứ đều đơn giản ở đây, hãy tạo một cái mới dockerfiles/hộp công cụ/Dockerfile với nội dung sau:

FROM alpine:3.11

RUN apk add --no-cache git git-crypt

RUN QBEC_VER=0.10.3 
 && wget -O- https://github.com/splunk/qbec/releases/download/v${QBEC_VER}/qbec-linux-amd64.tar.gz 
     | tar -C /tmp -xzf - 
 && mv /tmp/qbec /tmp/jsonnet-qbec /usr/local/bin/

RUN KUBECTL_VER=1.17.0 
 && wget -O /usr/local/bin/kubectl 
      https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VER}/bin/linux/amd64/kubectl 
 && chmod +x /usr/local/bin/kubectl

RUN HELM_VER=3.0.2 
 && wget -O- https://get.helm.sh/helm-v${HELM_VER}-linux-amd64.tar.gz 
     | tar -C /tmp -zxf - 
 && mv /tmp/linux-amd64/helm /usr/local/bin/helm

Như bạn có thể thấy, trong hình ảnh này, chúng tôi cài đặt tất cả các tiện ích mà chúng tôi đã sử dụng để triển khai ứng dụng của mình. Chúng tôi không cần nó ở đây trừ khi kubectl, nhưng bạn có thể muốn thử nghiệm nó trong giai đoạn thiết lập quy trình.

Ngoài ra, để có thể giao tiếp với Kubernetes và triển khai trên đó, chúng ta cần định cấu hình vai trò cho các nhóm do gitlab-runner tạo ra.

Để làm điều này, hãy vào thư mục có gitlab-runner:

cd deploy/gitlab-runner

và thêm một thành phần mới thành phần/rbac.jsonnet:

local env = {
  name: std.extVar('qbec.io/env'),
  namespace: std.extVar('qbec.io/defaultNs'),
};
local p = import '../params.libsonnet';
local params = p.components.rbac;

[
  {
    apiVersion: 'v1',
    kind: 'ServiceAccount',
    metadata: {
      labels: {
        app: params.name,
      },
      name: params.name,
    },
  },
  {
    apiVersion: 'rbac.authorization.k8s.io/v1',
    kind: 'Role',
    metadata: {
      labels: {
        app: params.name,
      },
      name: params.name,
    },
    rules: [
      {
        apiGroups: [
          '*',
        ],
        resources: [
          '*',
        ],
        verbs: [
          '*',
        ],
      },
    ],
  },
  {
    apiVersion: 'rbac.authorization.k8s.io/v1',
    kind: 'RoleBinding',
    metadata: {
      labels: {
        app: params.name,
      },
      name: params.name,
    },
    roleRef: {
      apiGroup: 'rbac.authorization.k8s.io',
      kind: 'Role',
      name: params.name,
    },
    subjects: [
      {
        kind: 'ServiceAccount',
        name: params.name,
        namespace: env.namespace,
      },
    ],
  },
]

Chúng tôi cũng sẽ mô tả các tham số mới trong môi trường/base.libsonnet, bây giờ trông như thế này:

local secrets = import '../secrets/base.libsonnet';

{
  components: {
    gitlabRunner: {
      name: 'gitlab-runner',
      values: {
        gitlabUrl: 'https://gitlab.com/',
        rbac: {
          create: true,
        },
        runnerRegistrationToken: secrets.runnerRegistrationToken,
        runners: {
          serviceAccountName: $.components.rbac.name,
          image: 'registry.gitlab.com/kvaps/docs.example.org/toolbox:v0.0.1',
        },
      },
    },
    rbac: {
      name: 'gitlab-runner-deploy',
    },
  },
}

Chú ý $.thành phần.rbac.name đề cập đến tên cho thành phần rbac

Hãy kiểm tra những gì đã thay đổi:

qbec diff default

và áp dụng các thay đổi của chúng tôi cho Kubernetes:

qbec apply default

Ngoài ra, đừng quên cam kết các thay đổi của chúng tôi với git:

cd ../..
git add dockerfiles/toolbox
git commit -m "Add Dockerfile for toolbox"
git add deploy/gitlab-runner
git commit -m "Configure gitlab-runner to use toolbox"

9. Quy trình đầu tiên của chúng tôi và tập hợp hình ảnh theo thẻ

Tại thư mục gốc của dự án, chúng tôi sẽ tạo .gitlab-ci.yml với nội dung sau:

.build_docker_image:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:debug-v0.15.0
    entrypoint: [""]
  before_script:
    - echo "{"auths":{"$CI_REGISTRY":{"username":"$CI_REGISTRY_USER","password":"$CI_REGISTRY_PASSWORD"}}}" > /kaniko/.docker/config.json

build_toolbox:
  extends: .build_docker_image
  script:
    - /kaniko/executor --cache --context $CI_PROJECT_DIR/dockerfiles/toolbox --dockerfile $CI_PROJECT_DIR/dockerfiles/toolbox/Dockerfile --destination $CI_REGISTRY_IMAGE/toolbox:$CI_COMMIT_TAG
  only:
    refs:
      - tags

build_website:
  extends: .build_docker_image
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  script:
    - /kaniko/executor --cache --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dockerfiles/website/Dockerfile --destination $CI_REGISTRY_IMAGE/website:$CI_COMMIT_TAG
  only:
    refs:
      - tags

Xin lưu ý chúng tôi sử dụng GIT_SUBMODULE_STRATEGY: bình thường dành cho những công việc mà bạn cần khởi tạo rõ ràng các mô-đun con trước khi thực thi.

Đừng quên cam kết những thay đổi của chúng tôi:

git add .gitlab-ci.yml
git commit -m "Automate docker build"

Tôi nghĩ chúng ta có thể gọi đây là một phiên bản một cách an toàn v0.0.1 và thêm thẻ:

git tag v0.0.1

Chúng tôi sẽ thêm thẻ bất cứ khi nào chúng tôi cần phát hành phiên bản mới. Các thẻ trong Docker image sẽ được gắn với thẻ Git. Mỗi lần đẩy với một thẻ mới sẽ khởi tạo quá trình xây dựng hình ảnh có thẻ này.

Hãy làm nó git đẩy --tagsvà hãy xem quy trình đầu tiên của chúng tôi:

Ảnh chụp màn hình của quy trình đầu tiên

Thử các công cụ mới để xây dựng và tự động triển khai trong Kubernetes

Điều đáng chú ý là thực tế là việc lắp ráp bằng thẻ phù hợp để xây dựng hình ảnh docker nhưng không phù hợp để triển khai ứng dụng lên Kubernetes. Vì các thẻ mới có thể được gán cho các cam kết cũ nên trong trường hợp này, việc khởi tạo quy trình cho chúng sẽ dẫn đến việc triển khai phiên bản cũ.

Để giải quyết vấn đề này, thông thường việc xây dựng hình ảnh docker được gắn với thẻ và việc triển khai ứng dụng đến một nhánh chủ, trong đó phiên bản của hình ảnh được thu thập được mã hóa cứng. Đây là nơi bạn có thể khởi tạo rollback bằng một thao tác hoàn nguyên đơn giản chủ-cành cây.

10. Tự động hóa triển khai

Để Gitlab-runner giải mã các bí mật của chúng tôi, chúng tôi sẽ cần xuất khóa kho lưu trữ và thêm nó vào các biến môi trường CI của mình:

git crypt export-key /tmp/docs-repo.key
base64 -w0 /tmp/docs-repo.key; echo

Chúng tôi sẽ lưu dòng kết quả trong Gitlab; để thực hiện việc này, hãy đi tới cài đặt dự án của chúng tôi:
Cài đặt -> CI/CD -> Biến

Và hãy tạo một biến mới:

Kiểu
Key
Giá trị
bảo vệ
Che mặt
Phạm vi

File
GITCRYPT_KEY
<your string>
true (trong quá trình đào tạo bạn có thể false)
true
All environments

Ảnh chụp màn hình của biến đã thêm

Thử các công cụ mới để xây dựng và tự động triển khai trong Kubernetes

Bây giờ hãy cập nhật của chúng tôi .gitlab-ci.yml thêm vào nó:

.deploy_qbec_app:
  stage: deploy
  only:
    refs:
      - master

deploy_gitlab_runner:
  extends: .deploy_qbec_app
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  before_script:
    - base64 -d "$GITCRYPT_KEY" | git-crypt unlock -
  script:
    - qbec apply default --root deploy/gitlab-runner --force:k8s-context __incluster__ --wait --yes

deploy_website:
  extends: .deploy_qbec_app
  script:
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes

Ở đây chúng tôi đã kích hoạt một số tùy chọn mới cho qbec:

  • --root một số/ứng dụng — cho phép bạn xác định thư mục của một ứng dụng cụ thể
  • --force:k8s-bối cảnh __incluster__ - đây là một biến ma thuật cho biết rằng việc triển khai sẽ diễn ra trong cùng một cụm mà gtilab-runner đang chạy. Điều này là cần thiết vì nếu không qbec sẽ cố gắng tìm máy chủ Kubernetes phù hợp trong kubeconfig của bạn
  • --Chờ đợi — buộc qbec phải đợi cho đến khi tài nguyên mà nó tạo ra chuyển sang trạng thái Sẵn sàng và chỉ sau đó thoát ra với mã thoát thành công.
  • -Đúng - chỉ cần vô hiệu hóa shell tương tác Bạn có chắc không? khi được triển khai.

Đừng quên cam kết những thay đổi của chúng tôi:

git add .gitlab-ci.yml
git commit -m "Automate deploy"

Và sau đẩy git chúng ta sẽ xem các ứng dụng của chúng ta đã được triển khai như thế nào:

Ảnh chụp màn hình của quy trình thứ hai

Thử các công cụ mới để xây dựng và tự động triển khai trong Kubernetes

11. Hiện vật và lắp ráp khi đẩy lên làm chủ

Thông thường, các bước được mô tả ở trên là đủ để xây dựng và phân phối hầu hết mọi vi dịch vụ nhưng chúng tôi không muốn thêm thẻ mỗi khi cần cập nhật trang web. Do đó, chúng tôi sẽ thực hiện lộ trình năng động hơn và thiết lập triển khai thông báo trong nhánh chính.

Ý tưởng rất đơn giản: bây giờ hình ảnh của chúng ta trang mạng sẽ được xây dựng lại mỗi khi bạn nhấn vào chủ, sau đó tự động triển khai lên Kubernetes.

Hãy cùng cập nhật hai công việc này trong .gitlab-ci.yml:

build_website:
  extends: .build_docker_image
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  script:
    - mkdir -p $CI_PROJECT_DIR/artifacts
    - /kaniko/executor --cache --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dockerfiles/website/Dockerfile --destination $CI_REGISTRY_IMAGE/website:$CI_COMMIT_REF_NAME --digest-file $CI_PROJECT_DIR/artifacts/website.digest
  artifacts:
    paths:
      - artifacts/
  only:
    refs:
      - master
      - tags

deploy_website:
  extends: .deploy_qbec_app
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST"

Xin lưu ý rằng chúng tôi đã thêm một chủ đề chủ к giới thiệu vì công việc xây dựng_trang web và bây giờ chúng tôi sử dụng $CI_COMMIT_REF_NAME thay vì $CI_COMMIT_TAG, tức là chúng ta đã được gỡ bỏ khỏi các thẻ trong Git và bây giờ chúng ta sẽ đẩy một hình ảnh có tên của nhánh cam kết đã khởi tạo đường dẫn. Điều đáng chú ý là điều này cũng sẽ hoạt động với các thẻ, điều này sẽ cho phép chúng tôi lưu ảnh chụp nhanh của một trang web với một phiên bản cụ thể trong sổ đăng ký docker.

Khi tên của thẻ docker cho phiên bản mới của trang web có thể không thay đổi, chúng tôi vẫn phải mô tả các thay đổi đối với Kubernetes, nếu không, nó sẽ không triển khai lại ứng dụng từ hình ảnh mới, vì nó sẽ không nhận thấy bất kỳ thay đổi nào trong bảng kê khai triển khai.

Lựa chọn —vm:ext-str dig=”$DIGEST” đối với qbec - cho phép bạn chuyển một biến ngoài sang jsonnet. Chúng tôi muốn nó được triển khai lại trong cụm với mỗi bản phát hành ứng dụng của chúng tôi. Chúng tôi không thể sử dụng tên thẻ nữa, tên này hiện có thể không thể thay đổi vì chúng tôi cần được gắn với một phiên bản cụ thể của hình ảnh và kích hoạt triển khai khi nó thay đổi.

Ở đây chúng ta sẽ được trợ giúp bởi khả năng lưu hình ảnh thông báo vào một tệp của Kaniko (tùy chọn --tập tin tiêu hóa)
Sau đó chúng ta sẽ chuyển file này và đọc nó tại thời điểm triển khai.

Hãy cập nhật các thông số cho chúng ta triển khai/trang web/môi trường/base.libsonnet bây giờ sẽ trông như thế này:

{
  components: {
    website: {
      name: 'example-docs',
      image: 'registry.gitlab.com/kvaps/docs.example.org/website@' + std.extVar('digest'),
      replicas: 1,
      containerPort: 80,
      servicePort: 80,
      nodeSelector: {},
      tolerations: [],
      ingressClass: 'nginx',
      domain: 'docs.example.org',
    },
  },
}

Xong, bây giờ bất kỳ cam kết nào trong chủ khởi tạo bản dựng hình ảnh docker cho trang mạng, sau đó triển khai nó lên Kubernetes.

Đừng quên cam kết những thay đổi của chúng tôi:

git add .
git commit -m "Configure dynamic build"

Chúng tôi sẽ kiểm tra sau đẩy git chúng ta sẽ thấy một cái gì đó như thế này:

Ảnh chụp màn hình đường dẫn dành cho chủ

Thử các công cụ mới để xây dựng và tự động triển khai trong Kubernetes

Về nguyên tắc, chúng ta không cần phải triển khai lại gitlab-runner sau mỗi lần đẩy, tất nhiên trừ khi không có gì thay đổi trong cấu hình của nó, hãy khắc phục nó trong .gitlab-ci.yml:

deploy_gitlab_runner:
  extends: .deploy_qbec_app
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  before_script:
    - base64 -d "$GITCRYPT_KEY" | git-crypt unlock -
  script:
    - qbec apply default --root deploy/gitlab-runner --force:k8s-context __incluster__ --wait --yes
  only:
    changes:
      - deploy/gitlab-runner/**/*

thay đổi sẽ cho phép bạn theo dõi những thay đổi trong triển khai/gitlab-runner/ và sẽ chỉ kích hoạt công việc của chúng tôi nếu có bất kỳ

Đừng quên cam kết những thay đổi của chúng tôi:

git add .gitlab-ci.yml
git commit -m "Reduce gitlab-runner deploy"

đẩy git, cái đó tốt hơn:

Ảnh chụp màn hình của quy trình cập nhật

Thử các công cụ mới để xây dựng và tự động triển khai trong Kubernetes

12. Môi trường năng động

Đã đến lúc đa dạng hóa quy trình của chúng tôi với môi trường năng động.

Đầu tiên hãy cập nhật công việc xây dựng_trang web trong của chúng tôi .gitlab-ci.yml, xóa khối khỏi nó có thể , điều này sẽ buộc Gitlab kích hoạt nó trên bất kỳ cam kết nào đối với bất kỳ nhánh nào:

build_website:
  extends: .build_docker_image
  variables:
    GIT_SUBMODULE_STRATEGY: normal
  script:
    - mkdir -p $CI_PROJECT_DIR/artifacts
    - /kaniko/executor --cache --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/dockerfiles/website/Dockerfile --destination $CI_REGISTRY_IMAGE/website:$CI_COMMIT_REF_NAME --digest-file $CI_PROJECT_DIR/artifacts/website.digest
  artifacts:
    paths:
      - artifacts/

Sau đó cập nhật công việc triển khai_trang web, thêm một khối ở đó môi trường:

deploy_website:
  extends: .deploy_qbec_app
  environment:
    name: prod
    url: https://docs.example.org
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST"

Điều này sẽ cho phép Gitlab liên kết công việc với sản phẩm môi trường và hiển thị liên kết chính xác đến nó.

Bây giờ hãy thêm hai công việc nữa:

deploy_website:
  extends: .deploy_qbec_app
  environment:
    name: prod
    url: https://docs.example.org
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply default --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST"

deploy_review:
  extends: .deploy_qbec_app
  environment:
    name: review/$CI_COMMIT_REF_NAME
    url: http://$CI_ENVIRONMENT_SLUG.docs.example.org
    on_stop: stop_review
  script:
    - DIGEST="$(cat artifacts/website.digest)"
    - qbec apply review --root deploy/website --force:k8s-context __incluster__ --wait --yes --vm:ext-str digest="$DIGEST" --vm:ext-str subdomain="$CI_ENVIRONMENT_SLUG" --app-tag "$CI_ENVIRONMENT_SLUG"
  only:
    refs:
    - branches
  except:
    refs:
      - master

stop_review:
  extends: .deploy_qbec_app
  environment:
    name: review/$CI_COMMIT_REF_NAME
    action: stop
  stage: deploy
  before_script:
    - git clone "$CI_REPOSITORY_URL" master
    - cd master
  script:
    - qbec delete review --root deploy/website --force:k8s-context __incluster__ --yes --vm:ext-str digest="$DIGEST" --vm:ext-str subdomain="$CI_ENVIRONMENT_SLUG" --app-tag "$CI_ENVIRONMENT_SLUG"
  variables:
    GIT_STRATEGY: none
  only:
    refs:
    - branches
  except:
    refs:
      - master
  when: manual

Chúng sẽ được khởi chạy khi được đẩy tới bất kỳ nhánh nào ngoại trừ nhánh chính và sẽ triển khai phiên bản xem trước của trang web.

Chúng tôi thấy một tùy chọn mới cho qbec: --thẻ ứng dụng — nó cho phép bạn gắn thẻ các phiên bản đã triển khai của ứng dụng và chỉ hoạt động trong thẻ này; khi tạo và hủy tài nguyên trong Kubernetes, qbec sẽ chỉ hoạt động với chúng.
Bằng cách này, chúng tôi không thể tạo môi trường riêng cho mỗi đánh giá mà chỉ cần sử dụng lại cùng một môi trường.

Ở đây chúng tôi cũng sử dụng đánh giá ứng tuyển qbec, thay vì qbec áp dụng mặc định - đây chính xác là thời điểm chúng tôi sẽ cố gắng mô tả sự khác biệt đối với môi trường của mình (xem xét và mặc định):

Thêm xem xét môi trường ở triển khai/trang web/qbec.yaml

spec:
  environments:
    review:
      defaultNamespace: docs
      server: https://kubernetes.example.org:8443

Sau đó chúng ta sẽ khai báo nó trong triển khai/trang web/params.libsonnet:

local env = std.extVar('qbec.io/env');
local paramsMap = {
  _: import './environments/base.libsonnet',
  default: import './environments/default.libsonnet',
  review: import './environments/review.libsonnet',
};

if std.objectHas(paramsMap, env) then paramsMap[env] else error 'environment ' + env + ' not defined in ' + std.thisFile

Và ghi lại các thông số tùy chỉnh cho nó trong triển khai/trang web/môi trường/review.libsonnet:

// this file has the param overrides for the default environment
local base = import './base.libsonnet';
local slug = std.extVar('qbec.io/tag');
local subdomain = std.extVar('subdomain');

base {
  components+: {
    website+: {
      name: 'example-docs-' + slug,
      domain: subdomain + '.docs.example.org',
    },
  },
}

Chúng ta cũng hãy xem xét kỹ hơn về jobu dừng_review, nó sẽ được kích hoạt khi nhánh bị xóa và do đó gitlab không cố kiểm tra xem nó đã được sử dụng chưa GIT_STRATEGY: không có, sau này chúng tôi nhân bản chủ-branch và xóa đánh giá thông qua nó.
Hơi khó hiểu một chút nhưng mình vẫn chưa tìm được cách nào hay hơn.
Một lựa chọn thay thế là triển khai từng đánh giá vào một vùng tên khách sạn, vùng này luôn có thể bị hủy hoàn toàn.

Đừng quên cam kết những thay đổi của chúng tôi:

git add .
git commit -m "Enable automatic review"

đẩy git, git checkout -b kiểm tra, kiểm tra nguồn gốc git đẩy, kiểm tra:

Ảnh chụp màn hình của môi trường đã tạo trong Gitlab

Thử các công cụ mới để xây dựng và tự động triển khai trong Kubernetes

Mọi thứ đang hoạt động? - tuyệt vời, hãy xóa nhánh thử nghiệm của chúng tôi: git tổng thể thanh toán, nguồn gốc đẩy git: kiểm tra, chúng tôi kiểm tra xem các tác vụ xóa môi trường có hoạt động không có lỗi hay không.

Ở đây tôi muốn làm rõ ngay rằng bất kỳ nhà phát triển nào trong dự án đều có thể tạo các nhánh, anh ta cũng có thể thay đổi .gitlab-ci.yml tập tin và truy cập các biến bí mật.
Do đó, chúng tôi đặc biệt khuyến nghị chỉ cho phép sử dụng chúng đối với các nhánh được bảo vệ, ví dụ như trong chủhoặc tạo một bộ biến riêng cho từng môi trường.

13. Đánh giá ứng dụng

Đánh giá ứng dụng Đây là tính năng GitLab cho phép bạn thêm một nút cho mỗi tệp trong kho lưu trữ để xem nhanh tệp đó trong môi trường được triển khai.

Để các nút này xuất hiện, bạn cần tạo một file .gitlab/route-map.yml và mô tả tất cả các phép biến đổi đường dẫn trong đó; trong trường hợp của chúng tôi, nó sẽ rất đơn giản:

# Indices
- source: /content/(.+?)_index.(md|html)/ 
  public: '1'

# Pages
- source: /content/(.+?).(md|html)/ 
  public: '1/'

Đừng quên cam kết những thay đổi của chúng tôi:

git add .gitlab/
git commit -m "Enable review apps"

đẩy git, và kiểm tra:

Ảnh chụp màn hình nút Đánh giá ứng dụng

Thử các công cụ mới để xây dựng và tự động triển khai trong Kubernetes

Công việc hoàn tất!

Nguồn dự án:

Cảm ơn bạn đã quan tâm, tôi hy vọng bạn thích nó Thử các công cụ mới để xây dựng và tự động triển khai trong Kubernetes

Nguồn: www.habr.com

Thêm một lời nhận xét