Quản trị Kubernetes on-premise (for newbie)
Lưu ý: Bài viết thuần tuý được viết góc nhìn kinh nghiệm của 1 middle đang 1 mình 1 ngựa quản trị 1 cụm k8s Dev, có thể có những điều còn sai sót, mong anh chị em nhẹ tay. Tiện đây em cũng đang kiếm job remote, làm đêm càng tốt, quý công ty có nhu cầu liên hệ em dưới comment ạ 😁
Mở đầu:
Với yêu cầu của công ty là dựng 1 cụm K8s để thay thế cho cụm production hiện tại đã quá lỗi thời do poorly maintained, kiến trúc ban đầu thiết kế rất khó để scale và fault tolerance rất kém, 1 devops (chưa đến middle) lúc đó (là em) đã phải ra tay để vẽ ra cho công ty một hệ thống tầm trung, mà đại để là anh chị em có thể nhìn vào diagram vẽ vội bên dưới

Cho anh chị em nào mắt kém thì nói chung là thiết kế tổng thể sẽ gồm:
- 3 cụm k8s và vài thành phần khác, bao gồm 1.1. Cụm Admin: bao gồm Rancher để manage các cụm con và các thành phần monitor/ logging 1.2. Cụm CICD: bao gồm Gitlab và các stack khác hỗ trợ việc lưu trữ code và triển khai ứng dụng 1.3. Các cụm App: như tên
- Các cụm nằm trên các dải mạng riêng biệt
- Các DB nằm trên các máy chủ riêng ở dải mạng nội bộ
- Sử dụng Longhorn làm block storage system
Với thiết kế trên thì các tính chất của nó đã giải quyết các yêu cầu:
- Quản lí tập trung: Các cụm được quản lí chung qua cụm Admin giúp việc quản lí và debug dễ thở hơn
- Triển khai phân tán: Các cụm phục vụ các mục đích/ môi trường khác nhau được tách riêng tăng fault tolerance và dễ dàng để scale, nhất là nếu doanh nghiệp có nhu cầu phục vụ multi tenant
Công ty cũng nói rõ hệ thống trên mang tinh thần thử nghiệm, nhưng với quyết tâm học để kiếm lương ngàn đô thì em đã cố làm mọi thứ theo cách khó nhất có thể. Bài viết cũng bao gồm vài kinh nghiệm chung chung đã đúc kết và cả các lỗi em đã và đang gặp với stack trên
Kinh nghiệm chung
Với 1 đứa trình độ junior lúc đó, việc triển khai 1 hệ thống như trên không phải là bất khả thi, nhưng cũng gặp khá nhiều khó khăn. Kinh nghiệm đầu tiên có lẽ là
Nghiên cứu kĩ và phân tích các options lúc cài đặt
Kiếp nạn đầu tiên mà em gặp phải đến từ Ingress của RKE2. Như mọi người đã biết là từ phiên bản 1.36, RKE2 cùng các distro k8s sẽ kết thúc vòng đời hỗ trợ cho Nginx Ingress. Nhưng ngặt nỗi lúc cài đặt cụm Rancher bằng terminal, có ai nói cho điều đó đâu, và default vẫn là Nginx Ingress. Chính lựa chọn này dẫn đến 1 quá trình update và revert vài lần mới xong để đổi qua Traefik Ingress (mặc định mới của RKE2). Chính việc không đọc kĩ các options cũng dẫn đến 1 sự cố đau đầu khác liên quan đến Longhorn (mà e có thể sẽ nói ở phần sau :v )
Với các bạn newbie, hãy tập đọc docs, đôi khi còn nhanh hơn là hỏi AI
Không dưới 3 lần việc đọc kĩ docs đã giúp em tìm ra giải pháp nhanh hơn là hỏi AI, vì
- AI chưa cập nhật kịp thay đổi trong phiên bản mới
- Tài liệu wording hơi tệ, AI hiểu sai Dùng AI không phải xấu, nhưng đừng quá phụ thuộc
Tools sẽ luôn thay đổi, hãy học tư duy
Mỗi công ty sẽ luôn có các stack khác nhau, cái quan trọng cần học ở thời điểm đầu là tư duy tự động hoá vốn quan trọng nhất với devops. Cái này thì chỉ có làm mới nghiệm ra, sẽ chẳng mấy khoá học dạy được
Các thử thách và sự cố đã và đang gặp phải
Gitlab runner với Kubernetes executor
Với tài nguyên có hạn (và lười xin thêm VM, em quyết định dùng Kubernetes cho gitlab runner. Ưu điểm của phương pháp này là tiết kiệm tài nguyên, kiểm tra logs nhanh chóng, dễ dàng chạy nhiều concurrent jobs, cũng như không cần sử dụng docker daemon ở node, giữ cho môi trường node sạch nhất có thể. Dưới đây là helm chart cho runner, được e viết lại theo docs (link). Nhược điểm tổng thời gian sẽ lâu hơn 1 chút do phải sinh thêm pod mới
gitlabUrl: https://gitlab.example.com
runnerRegistrationToken: "glrt-xxxxxxxx"
nodeSelector:
node-role: runner
rbac:
create: true
concurrent: 10
runners:
requestConcurrency: 10
config: |
listen_address = ":9252"
concurrent = 3
check_interval = 1
log_level = "debug"
log_format = "runner"
connection_max_age = "15m0s"
shutdown_timeout = 0
[session_server]
session_timeout = 1800
[[runners]]
name = "podman"
limit = 50
url = "https://gitlab.example.com/"
executor = "kubernetes"
builds_dir = "/my_custom_dir"
shell = "bash"
namespace= "gitlab-runner"
[runners.kubernetes]
host = ""
image_pull_secrets = ["image_pull_secrets"]
bearer_token_overwrite_allowed = false
image = ""
namespace = "gitlab-runner"
namespace_overwrite_allowed = ""
namespace_per_job = false
privileged = true
cache_dir = "/cache"
node_selector_overwrite_allowed = ""
node_tolerations_overwrite_allowed = ""
pod_labels_overwrite_allowed = ""
service_account_overwrite_allowed = ""
pod_annotations_overwrite_allowed = ""
[runners.kubernetes.node_selector]
"node-role" = "runner"
[runners.kubernetes.volumes]
[[runners.kubernetes.volumes.empty_dir]]
name = "repo"
mount_path = "/my_custom_dir"
[[runners.kubernetes.volumes.secret]]
name = "gitlab-domain-cert"
mount_path = "/custom-certs"
read_only = true
[[runners.kubernetes.volumes.pvc]]
name = "cache-pvc"
mount_path = "/cache"
[[runners.kubernetes.volumes.config_map]]
name = "custom-dockerfile"
mount_path = "/custom-dockerfile"
[runners.kubernetes.pod_security_context]
run_as_user = 1000
fs_group = 1000
[runners.kubernetes.build_container_security_context]
run_as_user = 1000
certsSecretName: gitlab-domain-cert
Như ở file trên có 1 đoạn mount 1 config map chưa make file và template cho các stack thường dùng ( [[runners.kubernetes.volumes.config_map]]), do đó dockerfile dùng cho production sẽ không đẩy thẳng vào code nếu muốn mà được gen ra trong lúc chạy CI
Gitlab báo random 5xx chập chờn dù vẫn thừa tài nguyên
Nếu cài đặt GItlab Helm bundle, hoặc trên server Postgres mới dựng/ không được dùng nhiều ( hoặc gitlab helm version 10. trở xuống), có thể ace sẽ gặp lỗi này (link).
Check logs trước ở postgres, khả năng cao sẽ báo lỗi
FATAL: remaining connection slots are reserved for non-replication superuser connections
Default MAX_CONNECTIONS của Postgres helm installation rất thấp, đơn giản là hãy nâng lên
postgresql:
primary:
extraEnvVars:
- name: POSTGRESQL_MAX_CONNECTIONS
value: "1024"
Làm tương tự nếu server Postgres đặt ở ngoài
Pod không thể mount lại vào PVC của longhorn
Nhất là với những PVC RWO mode, sau khi cập nhật pod có thể xảy ra tình trạng MountVolume.SetUp failed for volume, hoặc đơn giản logs pod có thể báo là đang có 1 pod khác đang occupy
Check logs như các bước trong link, nếu đúng là do multipathd thì có thể disable luôn
sudo systemctl stop multipathd && sudo systemctl disable multipathd
Lưu ý khi update Kubernetes với longhorn
Trên các node có chứa các pvc chỉ 1 replica, lúc rolling update k8s, sẽ thể drain hoàn toàn node, bởi policy mặc định của longhorn block-if-contains-last-replica.
Để xử lí hay đổi sang block-for-eviction-if-contains-last-replica, lưu ý luôn phải có 1 node đủ dung lượng để build lại replica đó trên node khác.
Để an toàn hãy luôn đảm bảo rằng mọi pvc có tối thiểu 2 replicas trên 2 node khác nhau.
Tips and tricks: trong trường hợp chỉ có duy nhất 1 node đủ storage cho pvc đó (pvc quá lớn), cta có thể đổi policy về allow-if-replica-is-stopped, nhưng pod gắn vào pvc đó phải ở trên cùng node với pvc đó, mục đích là khi evict pod đó thì replicas cũng sẽ được stop -> quá trình drain cũng sẽ được tiếp tục. Để chắn chắn cả 2 có thể cùng schedule trên cùng pod, tạm thời disable scheduling trên các pod longhorn lúc cài đặt, và giảm pvc replicas về 1
Traefik ingress stuck Progressing
Do Traefik còn khá mới, vẫn có vài bug nhỏ nhỏ như này. Lỗi này xảy ra do argo đọc field này trên resource để xem ingress đã heathy chưa status: loadBalancer: {}.
Như set up trên của e, TLS terminated ở Traefik, và tất cả traefik sẽ đi qua port 443 trên các node, không set LB status làm field trên bị trống -> Argo báo là progressing. Phần lớn mọi người có thể fix theo cách này (update traefik helm chart):
providers:
kubernetesIngress:
publishedService:
enabled: true
# Format: "namespace/traefik-service-name"
pathOverride: "traefik/traefik"
Hoặc đơn giản hơn cả là override để argo luôn báo heathy
data:
resource.customizations: |
networking.k8s.io/Ingress:
health.lua: |
hs = {}
hs.status = "Healthy"
hs.message = "Skip health check for Ingress"
return hs
Một vài quy trình tự động hoá và trò mèo để giảm thiểu workload
Chiến lược quản lí cert
Với cụm Admin, mặc định là sẽ không thể truy cập từ Internet, và cũng thường ít có nhu cầu truy cập từ xa, cũng như ace quản trị cũng sẽ không quan tâm đến dấu đỏ untrusted CA trên browser lắm, thì như em sẽ dùng selfsigned cert rồi config cho cert manager tự rotate luôn. Điều kiện là phải trust CA này trên tất cả các node mà cụm Admin quản trị, bao gồm node ở các cụm khác. Helm chart installation sẽ phải thêm privateCA: true để các node có thể trust CA khi join cụm
Ví dụ:
hostname: rancher.example.com
replicas: 1
ingress:
enabled: true
servicePort: 80
ingressClassName: "traefik"
tls:
source: secret
secretName: tls-rancher-ingress
extraAnnotations:
traefik.ingress.kubernetes.io/router.entrypoints: "websecure"
traefik.ingress.kubernetes.io/router.tls: "true"
privateCA: true
Với cert cho cụm App, hầu hết các cty phải mua cert wildcard từ các CA public, thì em dùng reflector để chỉ phải update 1 lần, thay đổi tất cả cert trên từng namesapce cho từng ứng dụng, đỡ phải mò từng namespace để thay
Argo instance cho mỗi cụm
Mỗi cụm sẽ có 1 argo instace riêng để quản lí các tools cài đặt trên cụm đó, trỏ đến repo chưa values file trên gitlab, set server side apply và manual sync, việc cập nhật chỉ bao gồm thay đổi param trên gitlab.
Kết
Hy vọng những kinh nghiệm e đúc kết được sẽ giúp đỡ phần nào cho anh chị em, nhất là các anh em mới chân ướt chân ráo vào nghề. Bài viết dài, còn ngô nghê do mới vào nghề chưa biết gì, mong ace lượng thứ và góp ý nhẹ tay
All rights reserved