Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Năm nay, hội nghị Kubernetes chính của Châu Âu - KubeCon + CloudNativeCon Europe 2020 - diễn ra trực tuyến. Tuy nhiên, sự thay đổi về hình thức như vậy không ngăn cản chúng tôi đưa ra báo cáo đã được lên kế hoạch từ lâu “Đi? Đánh! Gặp gỡ nhà điều hành Shell” dành riêng cho dự án Nguồn mở của chúng tôi người điều hành shell.

Bài viết này, lấy cảm hứng từ buổi nói chuyện, trình bày một cách tiếp cận để đơn giản hóa quy trình tạo toán tử cho Kubernetes và chỉ ra cách bạn có thể tự tạo toán tử của riêng mình với nỗ lực tối thiểu bằng cách sử dụng toán tử shell.

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Giới thiệu video báo cáo (~23 phút bằng tiếng Anh, nhiều thông tin hơn đáng kể so với bài viết) và đoạn trích chính từ nó ở dạng văn bản. Đi!

Tại Flant, chúng tôi liên tục tối ưu hóa và tự động hóa mọi thứ. Hôm nay chúng ta sẽ nói về một khái niệm thú vị khác. Gặp: tập lệnh shell gốc trên nền tảng đám mây!

Tuy nhiên, hãy bắt đầu với bối cảnh xảy ra tất cả những điều này: Kubernetes.

API Kubernetes và bộ điều khiển

API trong Kubernetes có thể được biểu diễn dưới dạng một loại máy chủ tệp với các thư mục cho từng loại đối tượng. Các đối tượng (tài nguyên) trên máy chủ này được thể hiện bằng các tệp YAML. Ngoài ra, máy chủ còn có API cơ bản cho phép bạn thực hiện ba việc:

  • nhận được tài nguyên theo loại và tên của nó;
  • thay đổi tài nguyên (trong trường hợp này, máy chủ chỉ lưu trữ các đối tượng "đúng" - tất cả các đối tượng được định dạng không chính xác hoặc dành cho các thư mục khác đều bị loại bỏ);
  • theo dõi đối với tài nguyên (trong trường hợp này, người dùng ngay lập tức nhận được phiên bản hiện tại/cập nhật của nó).

Do đó, Kubernetes hoạt động như một loại máy chủ tệp (dành cho tệp kê khai YAML) với ba phương thức cơ bản (vâng, thực tế còn có các phương thức khác, nhưng hiện tại chúng tôi sẽ bỏ qua chúng).

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Vấn đề là máy chủ chỉ có thể lưu trữ thông tin. Để làm cho nó hoạt động bạn cần điều khiển - khái niệm cơ bản và quan trọng thứ hai trong thế giới Kubernetes.

Có hai loại bộ điều khiển chính. Đầu tiên lấy thông tin từ Kubernetes, xử lý thông tin theo logic lồng nhau và trả về K8. Loại thứ hai lấy thông tin từ Kubernetes, nhưng không giống như loại thứ nhất, loại này thay đổi trạng thái của một số tài nguyên bên ngoài.

Chúng ta hãy xem xét kỹ hơn quá trình tạo Triển khai trong Kubernetes:

  • Bộ điều khiển triển khai (có trong kube-controller-manager) nhận thông tin về Triển khai và tạo Bản sao.
  • ReplicaSet tạo hai bản sao (hai nhóm) dựa trên thông tin này, nhưng các nhóm này chưa được lên lịch.
  • Bộ lập lịch lên lịch cho các nhóm và thêm thông tin nút vào YAML của chúng.
  • Kubelets thực hiện các thay đổi đối với tài nguyên bên ngoài (giả sử Docker).

Sau đó, toàn bộ trình tự này được lặp lại theo thứ tự ngược lại: kubelet kiểm tra các thùng chứa, tính toán trạng thái của nhóm và gửi lại. Bộ điều khiển ReplicaSet nhận trạng thái và cập nhật trạng thái của bộ bản sao. Điều tương tự cũng xảy ra với Bộ điều khiển triển khai và người dùng cuối cùng cũng nhận được trạng thái cập nhật (hiện tại).

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Người vận hành vỏ

Hóa ra Kubernetes dựa trên công việc chung của nhiều bộ điều khiển khác nhau (toán tử Kubernetes cũng là bộ điều khiển). Câu hỏi đặt ra là làm thế nào để tạo toán tử của riêng bạn với nỗ lực tối thiểu? Và đây là thứ chúng tôi phát triển đã ra tay giải cứu người điều hành shell. Nó cho phép quản trị viên hệ thống tạo các báo cáo của riêng họ bằng các phương pháp quen thuộc.

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Ví dụ đơn giản: sao chép bí mật

Hãy xem xét một ví dụ đơn giản.

Giả sử chúng ta có một cụm Kubernetes. Nó có một không gian tên default với một số bí mật mysecret. Ngoài ra, còn có các không gian tên khác trong cụm. Một số trong số chúng có nhãn cụ thể kèm theo. Mục tiêu của chúng tôi là sao chép Bí mật vào các không gian tên có nhãn.

Nhiệm vụ này phức tạp bởi thực tế là các không gian tên mới có thể xuất hiện trong cụm và một số trong số chúng có thể có nhãn này. Mặt khác, khi nhãn bị xóa thì Bí mật cũng nên bị xóa. Ngoài ra, bản thân Bí mật cũng có thể thay đổi: trong trường hợp này, Bí mật mới phải được sao chép vào tất cả các không gian tên có nhãn. Nếu Bí mật vô tình bị xóa trong bất kỳ không gian tên nào, nhà điều hành của chúng tôi sẽ khôi phục nó ngay lập tức.

Bây giờ nhiệm vụ đã được hình thành, đã đến lúc bắt đầu triển khai nó bằng cách sử dụng toán tử shell. Nhưng trước tiên, cần nói vài lời về chính toán tử shell.

Cách hoạt động của shell-operator

Giống như các khối lượng công việc khác trong Kubernetes, shell-operator chạy trong nhóm riêng của nó. Trong nhóm này trong thư mục /hooks các tập tin thực thi được lưu trữ. Đây có thể là các tập lệnh bằng Bash, Python, Ruby, v.v. Chúng tôi gọi các tập tin thực thi như vậy là hook (móc).

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Shell-operator đăng ký các sự kiện Kubernetes và chạy các hook này để phản hồi những sự kiện mà chúng ta cần.

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Làm thế nào để người vận hành shell biết nên chạy hook nào và khi nào? Vấn đề là mỗi cái móc đều có hai giai đoạn. Trong quá trình khởi động, toán tử shell chạy tất cả các hook có đối số --config Đây là giai đoạn cấu hình. Và sau đó, các móc được khởi chạy theo cách thông thường - để phản ứng với các sự kiện mà chúng được gắn vào. Trong trường hợp sau, hook nhận bối cảnh ràng buộc (bối cảnh ràng buộc) - dữ liệu ở định dạng JSON mà chúng ta sẽ nói chi tiết hơn bên dưới.

Tạo một toán tử trong Bash

Bây giờ chúng tôi đã sẵn sàng để thực hiện. Để làm điều này, chúng ta cần viết hai hàm (nhân tiện, chúng tôi khuyên bạn nên thư viện shell_lib, giúp đơn giản hóa rất nhiều việc viết hook trong Bash):

  • cái đầu tiên là cần thiết cho giai đoạn cấu hình - nó hiển thị bối cảnh ràng buộc;
  • cái thứ hai chứa logic chính của hook.

#!/bin/bash

source /shell_lib.sh

function __config__() {
  cat << EOF
    configVersion: v1
    # BINDING CONFIGURATION
EOF
}

function __main__() {
  # THE LOGIC
}

hook::run "$@"

Bước tiếp theo là quyết định những đồ vật chúng ta cần. Trong trường hợp của chúng tôi, chúng tôi cần theo dõi:

  • bí mật nguồn cho những thay đổi;
  • tất cả các không gian tên trong cụm, để bạn biết không gian tên nào có nhãn gắn liền với chúng;
  • nhắm mục tiêu bí mật để đảm bảo rằng tất cả chúng đều đồng bộ với bí mật nguồn.

Đăng ký nguồn bí mật

Cấu hình ràng buộc cho nó khá đơn giản. Chúng tôi cho biết rằng chúng tôi quan tâm đến Bí mật với tên mysecret trong không gian tên default:

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

function __config__() {
  cat << EOF
    configVersion: v1
    kubernetes:
    - name: src_secret
      apiVersion: v1
      kind: Secret
      nameSelector:
        matchNames:
        - mysecret
      namespace:
        nameSelector:
          matchNames: ["default"]
      group: main
EOF

Kết quả là hook sẽ được kích hoạt khi bí mật nguồn thay đổi (src_secret) và nhận bối cảnh ràng buộc sau:

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Như bạn có thể thấy, nó chứa tên và toàn bộ đối tượng.

Theo dõi không gian tên

Bây giờ bạn cần đăng ký không gian tên. Để thực hiện việc này, chúng tôi chỉ định cấu hình ràng buộc sau:

- name: namespaces
  group: main
  apiVersion: v1
  kind: Namespace
  jqFilter: |
    {
      namespace: .metadata.name,
      hasLabel: (
       .metadata.labels // {} |  
         contains({"secret": "yes"})
      )
    }
  group: main
  keepFullObjectsInMemory: false

Như bạn có thể thấy, một trường mới đã xuất hiện trong cấu hình có tên jqFilter. Đúng như tên gọi của nó, jqFilter lọc ra tất cả thông tin không cần thiết và tạo một đối tượng JSON mới với các trường mà chúng tôi quan tâm. Một hook có cấu hình tương tự sẽ nhận được bối cảnh ràng buộc sau:

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Nó chứa một mảng filterResults cho mỗi không gian tên trong cụm. Biến Boolean hasLabel cho biết liệu nhãn có được gắn vào một không gian tên nhất định hay không. Bộ chọn keepFullObjectsInMemory: false chỉ ra rằng không cần phải giữ các đối tượng hoàn chỉnh trong bộ nhớ.

Theo dõi bí mật mục tiêu

Chúng tôi đăng ký tất cả Bí mật có chú thích được chỉ định managed-secret: "yes" (đây là mục tiêu của chúng tôi dst_secrets):

- name: dst_secrets
  apiVersion: v1
  kind: Secret
  labelSelector:
    matchLabels:
      managed-secret: "yes"
  jqFilter: |
    {
      "namespace":
        .metadata.namespace,
      "resourceVersion":
        .metadata.annotations.resourceVersion
    }
  group: main
  keepFullObjectsInMemory: false

Trong trường hợp này, jqFilter lọc ra tất cả thông tin ngoại trừ không gian tên và tham số resourceVersion. Tham số cuối cùng được chuyển đến chú thích khi tạo bí mật: nó cho phép bạn so sánh các phiên bản bí mật và cập nhật chúng.

Một hook được cấu hình theo cách này, khi được thực thi, sẽ nhận được ba bối cảnh ràng buộc được mô tả ở trên. Chúng có thể được coi như một loại ảnh chụp nhanh (ảnh chụp) cụm.

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Dựa trên tất cả thông tin này, một thuật toán cơ bản có thể được phát triển. Nó lặp qua tất cả các không gian tên và:

  • nếu hasLabel vấn đề true cho không gian tên hiện tại:
    • so sánh bí mật toàn cầu với bí mật địa phương:
      • nếu chúng giống nhau thì chẳng có tác dụng gì;
      • nếu chúng khác nhau - thực thi kubectl replace hoặc create;
  • nếu hasLabel vấn đề false cho không gian tên hiện tại:
    • đảm bảo rằng Bí mật không nằm trong không gian tên đã cho:
      • nếu có Bí mật cục bộ, hãy xóa nó bằng cách sử dụng kubectl delete;
      • nếu Bí mật cục bộ không được phát hiện thì nó không làm gì cả.

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Triển khai thuật toán trong Bash bạn có thể tải xuống trong của chúng tôi kho lưu trữ với các ví dụ.

Đó là cách chúng tôi có thể tạo một bộ điều khiển Kubernetes đơn giản bằng cách sử dụng 35 dòng cấu hình YAML và cùng một lượng mã Bash! Công việc của người vận hành shell là liên kết chúng lại với nhau.

Tuy nhiên, sao chép bí mật không phải là lĩnh vực ứng dụng duy nhất của tiện ích này. Dưới đây là một vài ví dụ nữa để cho thấy khả năng của anh ấy.

Ví dụ 1: Thực hiện các thay đổi đối với ConfigMap

Chúng ta hãy xem Triển khai bao gồm ba nhóm. Pod sử dụng ConfigMap để lưu trữ một số cấu hình. Khi nhóm được khởi chạy, ConfigMap ở một trạng thái nhất định (hãy gọi nó là v.1). Theo đó, tất cả các nhóm đều sử dụng phiên bản ConfigMap cụ thể này.

Bây giờ hãy giả sử rằng ConfigMap đã thay đổi (v.2). Tuy nhiên, nhóm sẽ sử dụng phiên bản ConfigMap (v.1) trước đó:

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Làm cách nào tôi có thể khiến họ chuyển sang Bản đồ cấu hình mới (v.2)? Câu trả lời rất đơn giản: sử dụng một mẫu. Hãy thêm chú thích tổng kiểm tra vào phần này template Cấu hình triển khai:

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Do đó, tổng kiểm tra này sẽ được đăng ký trong tất cả các nhóm và nó sẽ giống như tổng kiểm tra của Triển khai. Bây giờ bạn chỉ cần cập nhật chú thích khi ConfigMap thay đổi. Và toán tử shell có ích trong trường hợp này. Tất cả những gì bạn cần làm là lập trình một hook sẽ đăng ký vào ConfigMap và cập nhật tổng kiểm tra.

Nếu người dùng thực hiện các thay đổi đối với Bản đồ cấu hình, trình điều khiển shell sẽ nhận thấy chúng và tính toán lại tổng kiểm tra. Sau đó, phép thuật của Kubernetes sẽ phát huy tác dụng: người điều phối sẽ giết nhóm, tạo một nhóm mới, đợi cho đến khi nó trở thành Ready, và chuyển sang cái tiếp theo. Kết quả là Triển khai sẽ đồng bộ hóa và chuyển sang phiên bản ConfigMap mới.

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Ví dụ 2: Làm việc với Định nghĩa tài nguyên tùy chỉnh

Như bạn đã biết, Kubernetes cho phép bạn tạo các loại đối tượng tùy chỉnh. Ví dụ: bạn có thể tạo loại MysqlDatabase. Giả sử loại này có hai tham số siêu dữ liệu: name и namespace.

apiVersion: example.com/v1alpha1
kind: MysqlDatabase
metadata:
  name: foo
  namespace: bar

Chúng tôi có một cụm Kubernetes với các không gian tên khác nhau để chúng tôi có thể tạo cơ sở dữ liệu MySQL. Trong trường hợp này, shell-operator có thể được sử dụng để theo dõi tài nguyên MysqlDatabase, kết nối chúng với máy chủ MySQL và đồng bộ hóa các trạng thái mong muốn và quan sát được của cụm.

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Ví dụ 3: Giám sát mạng cụm

Như bạn đã biết, sử dụng ping là cách đơn giản nhất để giám sát mạng. Trong ví dụ này, chúng tôi sẽ chỉ ra cách triển khai giám sát như vậy bằng cách sử dụng toán tử shell.

Trước hết, bạn sẽ cần phải đăng ký các nút. Toán tử shell cần tên và địa chỉ IP của mỗi nút. Với sự giúp đỡ của họ, anh ta sẽ ping các nút này.

configVersion: v1
kubernetes:
- name: nodes
  apiVersion: v1
  kind: Node
  jqFilter: |
    {
      name: .metadata.name,
      ip: (
       .status.addresses[] |  
        select(.type == "InternalIP") |
        .address
      )
    }
  group: main
  keepFullObjectsInMemory: false
  executeHookOnEvent: []
schedule:
- name: every_minute
  group: main
  crontab: "* * * * *"

Thông số executeHookOnEvent: [] ngăn hook chạy để phản hồi lại bất kỳ sự kiện nào (nghĩa là để phản hồi lại việc thay đổi, thêm, xóa các nút). Tuy nhiên, anh ấy sẽ chạy (và cập nhật danh sách các nút) Lên kế hoạch - mỗi phút, theo quy định của trường schedule.

Bây giờ câu hỏi đặt ra là làm thế nào chúng ta biết chính xác về các vấn đề như mất gói? Chúng ta hãy xem mã:

function __main__() {
  for i in $(seq 0 "$(context::jq -r '(.snapshots.nodes | length) - 1')"); do
    node_name="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.name')"
    node_ip="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.ip')"
    packets_lost=0
    if ! ping -c 1 "$node_ip" -t 1 ; then
      packets_lost=1
    fi
    cat >> "$METRICS_PATH" <<END
      {
        "name": "node_packets_lost",
        "add": $packets_lost,
        "labels": {
          "node": "$node_name"
        }
      }
END
  done
}

Chúng tôi duyệt qua danh sách các nút, lấy tên và địa chỉ IP của chúng, ping chúng và gửi kết quả đến Prometheus. Nhà điều hành Shell có thể xuất số liệu sang Prometheus, lưu chúng vào một tệp nằm theo đường dẫn được chỉ định trong biến môi trường $METRICS_PATH.

Ở đây bạn có thể tạo một toán tử để giám sát mạng đơn giản trong một cụm.

Cơ chế xếp hàng

Bài viết này sẽ không đầy đủ nếu không mô tả một cơ chế quan trọng khác được tích hợp trong toán tử shell. Hãy tưởng tượng rằng nó thực thi một số loại hook để phản hồi lại một sự kiện trong cụm.

  • Điều gì sẽ xảy ra nếu cùng lúc đó có điều gì đó xảy ra trong cụm? một lần nữa sự kiện?
  • Trình điều khiển shell có chạy một phiên bản khác của hook không?
  • Điều gì sẽ xảy ra nếu năm sự kiện xảy ra trong cụm cùng một lúc?
  • Liệu trình điều hành shell có xử lý chúng song song không?
  • Còn các tài nguyên đã tiêu thụ như bộ nhớ và CPU thì sao?

May mắn thay, shell-operator có cơ chế xếp hàng tích hợp sẵn. Tất cả các sự kiện được xếp hàng đợi và xử lý tuần tự.

Hãy minh họa điều này bằng các ví dụ. Giả sử chúng ta có hai cái móc. Sự kiện đầu tiên đi tới hook đầu tiên. Sau khi quá trình xử lý hoàn tất, hàng đợi sẽ di chuyển về phía trước. Ba sự kiện tiếp theo được chuyển hướng đến hook thứ hai - chúng được xóa khỏi hàng đợi và được nhập vào đó trong một “gói”. Đó là hook nhận được một loạt các sự kiện - hay chính xác hơn là một loạt các bối cảnh ràng buộc.

Ngoài ra những các sự kiện có thể được kết hợp thành một sự kiện lớn. Tham số chịu trách nhiệm cho việc này group trong cấu hình ràng buộc.

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Bạn có thể tạo bất kỳ số lượng hàng đợi/móc nào và các kết hợp khác nhau của chúng. Ví dụ: một hàng đợi có thể hoạt động với hai móc hoặc ngược lại.

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Tất cả những gì bạn cần làm là định cấu hình trường phù hợp queue trong cấu hình ràng buộc. Nếu tên hàng đợi không được chỉ định, hook sẽ chạy trên hàng đợi mặc định (default). Cơ chế xếp hàng này cho phép bạn giải quyết triệt để mọi vấn đề về quản lý tài nguyên khi làm việc với hook.

Kết luận

Chúng tôi đã giải thích shell-operator là gì, chỉ ra cách sử dụng nó để tạo các toán tử Kubernetes một cách nhanh chóng và dễ dàng, đồng thời đưa ra một số ví dụ về cách sử dụng nó.

Thông tin chi tiết về toán tử shell cũng như hướng dẫn nhanh về cách sử dụng nó có sẵn trong tài liệu tương ứng. kho lưu trữ trên GitHub. Đừng ngần ngại liên hệ với chúng tôi nếu có thắc mắc: bạn có thể thảo luận chúng một cách đặc biệt Nhóm điện tín (bằng tiếng Nga) hoặc bằng diễn đàn này (bằng tiếng Anh).

Và nếu bạn thích nó, chúng tôi luôn vui mừng khi thấy các vấn đề/PR/ngôi sao mới trên GitHub, nhân tiện, bạn có thể tìm thấy những vấn đề khác ở đâu dự án thú vị. Trong số đó đáng chú ý toán tử bổ sung, là anh lớn của shell-operator. Tiện ích này sử dụng biểu đồ Helm để cài đặt các tiện ích bổ sung, có thể cung cấp các bản cập nhật và giám sát các tham số/giá trị khác nhau của biểu đồ, kiểm soát quá trình cài đặt biểu đồ và cũng có thể sửa đổi chúng để phản hồi các sự kiện trong cụm.

Đi? Đánh! Gặp gỡ nhà điều hành shell (đánh giá và báo cáo video từ KubeCon EU'2020)

Video và slide

Video màn trình diễn (~23 phút):


Trình bày báo cáo:

PS

Đọc thêm trên blog của chúng tôi:

Nguồn: www.habr.com

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