> ## Documentation Index
> Fetch the complete documentation index at: https://docs.befailproof.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Kubernetes deployment

title: "Hướng dẫn triển khai Kubernetes"
description: "Tài liệu hướng dẫn triển khai AgentEye trên Kubernetes."
----------------------------------------------------------------------

Hướng dẫn này triển khai toàn bộ ngăn xếp AgentEye lên một cụm Kubernetes chuyên dụng:

* **ClickHouse 24.8** -- kho lưu trữ phân tích chính cho sự kiện và đánh giá (StatefulSet với khối lưu trữ bền vững 100Gi). Bắt buộc: máy chủ từ chối khởi động mà không có nó.
* **PostgreSQL 16** -- kho lưu trữ quan hệ/siêu dữ liệu cho tổ chức, khóa API, người dùng, bảng điều khiển, truy vấn đã lưu và xác thực (StatefulSet với khối lưu trữ bền vững 50Gi)
* **Redis 7.2** -- bộ nhớ đệm chia sẻ tùy chọn và phần phụ trợ giới hạn tốc độ; máy chủ và bảng điều khiển sẽ hoạt động bình thường nếu nó không khả dụng
* **AgentEye Server** -- API Rust để thu nhận sự kiện, phân tích và quản lý khóa (2 bản sao)
* **AgentEye Dashboard** -- UI web Next.js (2 bản sao)
* **AI assistant (dịch vụ agent)** -- trợ lý chỉ đọc tùy chọn trong bảng điều khiển trên cổng 9100; vô hoạt cho đến khi điểm cuối LLM được cấu hình
* **Traefik (công khai)** -- bộ điều khiển ingress cho lưu lượng trạm thu thập, được bảo vệ bằng mTLS
* **Traefik (bảng điều khiển)** -- bộ điều khiển ingress cho bảng điều khiển, chỉ VPN/danh sách cho phép IP
* **cert-manager** -- chứng chỉ TLS và CA mTLS
* **Backup CronJob** -- xả khoá hợp nhất hàng ngày của PostgreSQL + ClickHouse lúc 03:00 UTC
* **Cert Renewal Monitor** -- cảnh báo khi chứng chỉ khách hàng sắp hết hạn

**Thời gian ước tính:** 60–90 phút cho lần triển khai đầu tiên.

Đối với mô hình triển khai được quản lý mà Exosphere xử lý tất cả các điều này thay bạn, hãy xem [enterprise-docs/managed-deployment.md](/vi/agenteye/managed-deployment).

***

## Điều kiện tiên quyết

Chạy từng lệnh xác minh trước khi bắt đầu. Mọi kiểm tra phải vượt qua.

| Yêu cầu                               | Tối thiểu                                            | Lệnh xác minh                                                      | Kết quả dự kiến                                                                |
| ------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
| Cụm Kubernetes                        | 1.27+                                                | `kubectl version`                                                  | Server Version >= v1.27                                                        |
| Kustomize (được đóng gói với kubectl) | Kustomize v1.14+ (được đóng gói trong kubectl 1.27+) | `kubectl kustomize --help`                                         | In ra văn bản sử dụng                                                          |
| Helm                                  | v3                                                   | `helm version`                                                     | `Version:"v3.x.x"`                                                             |
| RBAC cluster-admin                    | --                                                   | `kubectl auth can-i create namespaces`                             | `yes`                                                                          |
| StorageClass mặc định                 | --                                                   | `kubectl get storageclass`                                         | Ít nhất một hàng được đánh dấu `(default)`                                     |
| Hỗ trợ LoadBalancer                   | --                                                   | Phụ thuộc vào đám mây (EKS, GKE, AKS đều hỗ trợ theo mặc định)     | --                                                                             |
| GitHub PAT                            | --                                                   | `echo $AGENTEYE_TOKEN`                                             | Không trống (xem [enterprise-docs/github-token.md](/vi/agenteye/github-token)) |
| openssl                               | --                                                   | `openssl version`                                                  | OpenSSL 1.x hoặc 3.x                                                           |
| Bộ lưu trữ cloud                      | --                                                   | Dành cho sao lưu PostgreSQL + ClickHouse (S3, GCS hoặc Azure Blob) | --                                                                             |

**Kích thước cụm:** Tối thiểu 3 nút, mỗi nút 4 vCPU / 8 GB RAM. Xem [enterprise-docs/managed-deployment.md](/vi/agenteye/managed-deployment) để xem yêu cầu đầy đủ.

### Chạy tất cả các kiểm tra cùng một lúc

```bash theme={null}
echo "--- Prerequisites Check ---"
kubectl version --client -o yaml 2>/dev/null | grep -q gitVersion && echo "PASS: kubectl" || echo "FAIL: kubectl not found"
helm version --short 2>/dev/null | grep -q v3 && echo "PASS: helm v3" || echo "FAIL: helm v3 not found"
kubectl auth can-i create namespaces 2>/dev/null | grep -q yes && echo "PASS: cluster-admin" || echo "FAIL: no cluster-admin"
kubectl get storageclass 2>/dev/null | grep -q default && echo "PASS: default StorageClass" || echo "FAIL: no default StorageClass"
[ -n "$AGENTEYE_TOKEN" ] && echo "PASS: AGENTEYE_TOKEN set" || echo "FAIL: AGENTEYE_TOKEN not set"
openssl version >/dev/null 2>&1 && echo "PASS: openssl" || echo "FAIL: openssl not found"
echo "---"
```

### Hình dạng triển khai

**Điểm cuối tiêu thụ** được phục vụ trên tên máy chủ mà bạn kiểm soát (ví dụ: `ingest.your-company.example`). cert-manager yêu cầu chứng chỉ TLS được tin cậy công khai từ Let's Encrypt qua HTTP-01, vì vậy trạm thu thập sẽ xác minh chứng chỉ máy chủ so với kho tin cậy hệ thống, không cần ghim CA theo khách hàng.

**Điểm cuối bảng điều khiển** hoạt động theo cách tương tự: nó được phục vụ trên tên máy chủ thứ hai mà bạn kiểm soát (ví dụ: `agenteye.your-company.example`) chỉ đến bộ cân bằng tải Traefik bảng điều khiển, và cert-manager phát hành chứng chỉ Let's Encrypt của nó qua bộ cân bằng tải đó. Các trình duyệt nhận được chứng chỉ được tin cậy mà không có cảnh báo.

> **Cấp phát và gia hạn chứng chỉ xác thực qua HTTP-01**, vì vậy cả hai LoadBalancer phải có thể truy cập được từ Internet công khai trên cổng 80. Nếu bạn cần hạn chế IP cho bộ cân bằng tải bảng điều khiển, hãy phối hợp trình giải quyết DNS-01 với hỗ trợ trước — nếu không, việc gia hạn sẽ thất bại im lặng và chứng chỉ hết hạn.

***

## Lấy các tệp kê khai

```bash theme={null}
git clone https://x:${AGENTEYE_TOKEN}@github.com/agenteye-enterprise/releases.git
cd releases/deploy
```

**Kiểm tra nó:**

```bash theme={null}
ls base/kustomization.yaml
```

Kết quả dự kiến: tệp tồn tại. Nếu không, bản sao đã thất bại -- kiểm tra `AGENTEYE_TOKEN` của bạn.

**Cấu trúc thư mục:**

```
deploy/
  base/           Cơ sở Kustomize chia sẻ (tất cả tài nguyên K8s)
  overlays/       Ghi đè cụ thể cụm (thẻ hình ảnh, tên máy chủ, tài nguyên)
  third-party/    Giá trị Helm cho Traefik, cert-manager và (tùy chọn) giám sát sức khỏe Robusta
```

**base** chứa mọi tài nguyên cần thiết cho triển khai đầy đủ, bao gồm chứng chỉ Let's Encrypt cho hai tên máy chủ công khai mà bạn cấu hình trong Giai đoạn 3.1. **overlay** sửa chữa cơ sở cho một môi trường cụ thể (ví dụ: thẻ hình ảnh tùy chỉnh, giới hạn tài nguyên, dây nối env). Thư mục **third-party** chứa các tệp giá trị Helm cho cơ sở hạ tầng bên ngoài.

> **Giám sát sức khỏe (tùy chọn):** bộ thăm dò sẵn sàng của máy chủ đã phản ánh sức khỏe Postgres + ClickHouse, và `third-party/robusta/` thêm cảnh báo lỗi pod gốc Kubernetes tùy chọn cho Slack. Xem [enterprise-docs/health-monitoring.md](/vi/agenteye/health-monitoring).

***

## Giai đoạn 1 -- Cơ sở hạ tầng bên thứ ba (\~30 phút)

### 1.1 Cài đặt cert-manager

cert-manager quản lý chứng chỉ TLS cho HTTPS và CA riêng được sử dụng cho chứng chỉ khách hàng mTLS.

```bash theme={null}
helm repo add jetstack https://charts.jetstack.io
helm repo update

helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager --create-namespace \
  --set crds.install=true
```

**Kiểm tra nó:**

```bash theme={null}
kubectl get pods -n cert-manager
```

Kết quả dự kiến: 3 pod đều `Running` -- `cert-manager`, `cert-manager-cainjector`, `cert-manager-webhook`.

```bash theme={null}
kubectl get crds | grep cert-manager
```

Kết quả dự kiến: ít nhất `certificates.cert-manager.io`, `clusterissuers.cert-manager.io`, `issuers.cert-manager.io`.

**Nếu nó không thành công:** Pod ở `CrashLoopBackOff` thường có nghĩa là CRD không được cài đặt. Chạy lại với `--set crds.install=true`. Nếu pod webhook không thành công trong kiểm tra sẵn sàng, chờ 30 giây và kiểm tra lại -- chúng có thể mất một chút thời gian để khởi động.

***

### 1.2 Cài đặt Traefik -- Bộ điều khiển tiêu thụ công khai

Thực thể Traefik này xử lý lưu lượng trạm thu thập trên LoadBalancer **bên ngoài**. Nó chấm dứt TLS và thực thi mTLS (xác minh chứng chỉ khách hàng) trên điểm cuối tiêu thụ.

```bash theme={null}
helm repo add traefik https://traefik.github.io/charts
helm repo update

helm install traefik-public traefik/traefik \
  --namespace traefik-public --create-namespace \
  --version 39.0.8 \
  -f third-party/traefik/values-public.yaml
```

**Kiểm tra nó:**

```bash theme={null}
kubectl get pods -n traefik-public
```

Kết quả dự kiến: 1 pod `Running`.

```bash theme={null}
kubectl get ingressclass traefik-public
```

Kết quả dự kiến: IngressClass tồn tại (nó không phải là lớp mặc định).

**Nếu nó không thành công:** Kiểm tra `kubectl describe pod -n traefik-public <pod-name>` để xem lỗi kéo hình ảnh hoặc ràng buộc tài nguyên.

***

### 1.3 Cài đặt Traefik -- Bộ điều khiển bảng điều khiển

Thực thể Traefik này phục vụ bảng điều khiển trên LoadBalancer chuyên dụng, bị hạn chế bởi danh sách cho phép IP.

> **Hai cơ chế danh sách cho phép được cung cấp cho thực thể này.** Hướng dẫn này sử dụng `values-dashboard.yaml`, giới hạn quyền truy cập bằng trường `service.loadBalancerSourceRanges` di động. Một `values-internal.yaml` song song cũng được cung cấp cho các môi trường AWS thích chú thích `service.beta.kubernetes.io/aws-load-balancer-source-ranges` thay thế. Chọn một và sử dụng nó một cách nhất quán; các bước dưới đây giả định `values-dashboard.yaml`.

**Trước khi cài đặt**, hãy chỉnh sửa `third-party/traefik/values-dashboard.yaml` để đặt các IP nguồn được phép. Trường `loadBalancerSourceRanges` kiểm soát IP nào có thể truy cập bảng điều khiển. Theo mặc định, nó được đặt thành `0.0.0.0/0` (tất cả IP); hạn chế nó thành VPN, văn phòng hoặc IP xuất hiện đã biết của bạn.

#### Danh sách cho phép một IP duy nhất

```yaml theme={null}
service:
  loadBalancerSourceRanges:
    - "<YOUR_VPN_IP>/32"
```

#### Danh sách cho phép nhiều IP

Thêm một mục trên mỗi IP hoặc khối CIDR. Hậu tố `/32` khớp với một địa chỉ IPv4 duy nhất; một khối CIDR (ví dụ: `/24`) khớp với một phạm vi. Bạn có thể trộn các IP riêng lẻ và phạm vi tự do:

```yaml theme={null}
service:
  loadBalancerSourceRanges:
    - "203.0.113.10/32"       # cổng văn phòng
    - "203.0.113.11/32"       # cổng văn phòng dự phòng
    - "198.51.100.0/24"       # bộ VPN
    - "192.0.2.50/32"         # IP nhà kỹ sư trực
```

Mẹo khi duy trì danh sách:

* Giữ một mục trên mỗi dòng và thêm một bình luận `#` ngắn để xác định chủ sở hữu hoặc mục đích của mỗi IP; đây là những gì các nhà khai thác trong tương lai sử dụng để quyết định xem mục nhập còn cần thiết hay không.
* Luôn sử dụng ký hiệu CIDR. Một IP trần như `203.0.113.10` bị nhà cung cấp đám mây từ chối; hãy sử dụng `203.0.113.10/32`.
* Đối với các phạm vi IPv6, hãy sử dụng CIDR tương đương `/128` (địa chỉ duy nhất) hoặc lớn hơn, ví dụ: `2001:db8::1/128`. Không phải tất cả các nhà cung cấp đám mây đều hỗ trợ phạm vi nguồn IPv6; kiểm tra tài liệu LoadBalancer của nhà cung cấp của bạn.
* Danh sách là **HOẶC**: lưu lượng được phép nếu nguồn khớp với bất kỳ mục nhập nào.

Sau khi chỉnh sửa tệp, tiếp tục với `helm install` dưới đây. Nếu bộ điều khiển đã được cài đặt, hãy chạy `helm upgrade` với các cờ tương tự hoặc vá Service lúc chạy (phần tiếp theo).

#### Cập nhật danh sách cho phép lúc chạy

Bạn có thể thay đổi các IP được phép mà không cần nâng cấp Helm bằng cách vá Service trực tiếp. **Bản vá thay thế toàn bộ danh sách**; luôn bao gồm mọi IP bạn muốn giữ, không chỉ cái mới.

Để thay thế danh sách bằng một bộ IP mới:

```bash theme={null}
kubectl patch svc traefik-dashboard -n traefik-dashboard \
  -p '{"spec":{"loadBalancerSourceRanges":["203.0.113.10/32","198.51.100.0/24","192.0.2.50/32"]}}'
```

Để an toàn **thêm** một IP mà không mất các mục nhập hiện có, trước tiên hãy đọc danh sách hiện tại, sau đó vá bằng bộ kết hợp:

```bash theme={null}
# 1. Hiển thị danh sách cho phép hiện tại
kubectl get svc traefik-dashboard -n traefik-dashboard \
  -o jsonpath='{.spec.loadBalancerSourceRanges}'

# 2. Vá bằng danh sách đầy đủ bao gồm IP mới
kubectl patch svc traefik-dashboard -n traefik-dashboard \
  -p '{"spec":{"loadBalancerSourceRanges":["<EXISTING_IP_1>/32","<EXISTING_IP_2>/32","<NEW_IP>/32"]}}'
```

> Các bản vá lúc chạy không được giữ lại trong `values-dashboard.yaml`. Để giữ thay đổi trong các nâng cấp Helm trong tương lai, cũng cập nhật tệp giá trị và cam kết nó.

Sau đó, cài đặt:

```bash theme={null}
helm install traefik-dashboard traefik/traefik \
  --namespace traefik-dashboard --create-namespace \
  --version 39.0.8 \
  -f third-party/traefik/values-dashboard.yaml
```

**Kiểm tra nó:**

```bash theme={null}
kubectl get pods -n traefik-dashboard
```

Kết quả dự kiến: 1 pod `Running`.

```bash theme={null}
kubectl get ingressclass traefik-dashboard
```

Kết quả dự kiến: IngressClass tồn tại.

***

### 1.4 Chờ LoadBalancer

Cả hai thực thể Traefik đều cần các IP bên ngoài trước khi tiếp tục.

```bash theme={null}
kubectl get svc -n traefik-public
kubectl get svc -n traefik-dashboard
```

**Kiểm tra nó:** Cả hai dịch vụ đều hiển thị một `EXTERNAL-IP` (không phải `<pending>`).

Nếu vẫn chưa giải quyết, hãy theo dõi phân công:

```bash theme={null}
kubectl get svc -n traefik-public -w
```

Nhấn `Ctrl+C` sau khi IP xuất hiện. Phân công IP thường mất 2–5 phút.

**Nếu nó không thành công:** `<pending>` sau 10 phút thường có nghĩa là nhà cung cấp đám mây không thể cấp phát LoadBalancer. Kiểm tra: thẻ con nít (EKS yêu cầu `kubernetes.io/role/elb`), cấu hình VPC, hạn ngạch dịch vụ và chú thích LB nội bộ chính xác được đặt cho thực thể nội bộ.

***

## Giai đoạn 2 -- Tạo bí mật (\~10 phút)

Tất cả các bí mật được tạo thủ công trước khi triển khai ứng dụng. Điều này đảm bảo các giá trị nhạy cảm không bao giờ xuất hiện trong các tệp kê khai.

### 2.1 Tạo không gian tên

```bash theme={null}
kubectl create namespace agenteye
```

**Kiểm tra nó:**

```bash theme={null}
kubectl get namespace agenteye
```

Kết quả dự kiến: trạng thái `Active`.

***

### 2.2 Bí mật kéo hình ảnh

Bí mật này xác thực với `ghcr.io` để kéo các hình ảnh vùng chứa AgentEye. Xem [enterprise-docs/github-token.md](/vi/agenteye/github-token) để biết cách tạo PAT của bạn.

```bash theme={null}
kubectl create secret docker-registry agenteye-image-pull \
  --namespace agenteye \
  --docker-server=ghcr.io \
  --docker-username=agenteye-enterprise \
  --docker-password="${AGENTEYE_TOKEN}"
```

**Kiểm tra nó:**

```bash theme={null}
kubectl get secret agenteye-image-pull -n agenteye -o jsonpath='{.type}'
```

Kết quả dự kiến: `kubernetes.io/dockerconfigjson`.

**Kiểm tra nó (sâu)** -- xác minh mã thông báo có thể thực sự kéo hình ảnh không:

Sử dụng thẻ hình ảnh `server` được ghim trong kustomization.yaml của overlay của bạn (hiện đang `v0.0.1-beta.48` trong cả lớp phủ `acme` được đóng gói và triển khai cơ sở). Thay thế thẻ bên dưới bằng thẻ bạn đang triển khai để kiểm tra này không trôi dạt qua các bản phát hành:

```bash theme={null}
kubectl run test-pull \
  --image=ghcr.io/agenteye-enterprise/server:v0.0.1-beta.48 \
  --overrides='{"spec":{"imagePullSecrets":[{"name":"agenteye-image-pull"}]}}' \
  --restart=Never -n agenteye --command -- echo ok

# Chờ một vài giây để kéo, sau đó:
kubectl logs test-pull -n agenteye
kubectl delete pod test-pull -n agenteye
```

Kết quả dự kiến: `ok` in trong nhật ký.

**Nếu nó không thành công:** `ErrImagePull` hoặc `401 Unauthorized` có nghĩa là PAT không hợp lệ hoặc thiếu phạm vi `read:packages`. Kiểm tra lại [enterprise-docs/github-token.md](/vi/agenteye/github-token).

***

### 2.3 Thông tin đăng nhập PostgreSQL

```bash theme={null}
POSTGRES_PASSWORD=$(openssl rand -hex 24)

kubectl create secret generic agenteye-postgres \
  --namespace agenteye \
  --from-literal=POSTGRES_USER=postgres \
  --from-literal=POSTGRES_PASSWORD="${POSTGRES_PASSWORD}" \
  --from-literal=POSTGRES_DB=agenteye
```

> **Quan trọng:** Chúng tôi sử dụng `-hex` (không phải `-base64`) để tạo mật khẩu. Đầu ra Base64 có thể chứa `+`, `/` và `=` khiến chuỗi kết nối `DATABASE_URL` bị hỏng. Xem [enterprise-docs/troubleshooting.md](/vi/agenteye/troubleshooting) để biết chi tiết.

> **Lưu trữ `POSTGRES_PASSWORD` trong trình quản lý bí mật của bạn ngay lập tức.** Bạn sẽ cần nó nếu bạn bao giờ khôi phục từ bản sao lưu hoặc kết nối trực tiếp với cơ sở dữ liệu.

**Kiểm tra nó:**

```bash theme={null}
kubectl get secret agenteye-postgres -n agenteye
```

Kết quả dự kiến: bí mật tồn tại.

```bash theme={null}
kubectl get secret agenteye-postgres -n agenteye \
  -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d | wc -c
```

Kết quả dự kiến: `48` (24 byte hex = 48 ký tự).

***

### 2.4 Khóa API quản trị

```bash theme={null}
ADMIN_KEY=$(openssl rand -hex 32)

kubectl create secret generic agenteye-admin-key \
  --namespace agenteye \
  --from-literal=ADMIN_KEY="${ADMIN_KEY}"
```

Khóa quản trị là thông tin đăng nhập bootstrap. Máy chủ upserts nó ở mỗi lần khởi động với tất cả các quyền. Sử dụng nó để tạo khóa trạm thu thập được xác định phạm vi trong Giai đoạn 7. Xem [enterprise-docs/api-keys.md](/vi/agenteye/api-keys) để xem mô hình quyền đầy đủ.

> **Lưu trữ `ADMIN_KEY` trong trình quản lý bí mật của bạn ngay lập tức.**

**Kiểm tra nó:**

```bash theme={null}
kubectl get secret agenteye-admin-key -n agenteye
```

Kết quả dự kiến: bí mật tồn tại.

***

### 2.5 Cấu hình xác thực (đăng nhập bảng điều khiển)

Bảng điều khiển sử dụng email + OTP cho đăng nhập người dùng. Nếu không có bí mật này, máy chủ vẫn khởi động và đường dẫn API `ADMIN_KEY` tiếp tục hoạt động, nhưng **không có người dùng nào có thể đăng nhập thông qua UI**.

Tất cả các khóa được tham chiếu là `optional: true` trong bản kê khai cơ sở, vì vậy các bí mật riêng phần (hoặc không có bí mật nào cả) là được; máy chủ quay lại các giá trị mặc định được ghi chép. Gói mọi thứ vào một bí mật `agenteye-auth` duy nhất giúp bề mặt xác thực có thể xoay trong một địa điểm.

```bash theme={null}
kubectl create secret generic agenteye-auth \
  --namespace agenteye \
  --from-literal=ADMIN_EMAIL="admin@yourcompany.com" \
  --from-literal=ALLOWED_EMAILS="*@yourcompany.com" \
  --from-literal=SMTP_HOST="smtp.yourprovider.com" \
  --from-literal=SMTP_PORT="587" \
  --from-literal=SMTP_USERNAME="your-smtp-user" \
  --from-literal=SMTP_PASSWORD="your-smtp-password" \
  --from-literal=SMTP_FROM="noreply@yourcompany.com" \
  --from-literal=SMTP_TLS="starttls" \
  --from-literal=DEFAULT_ORG_NAME="Acme Corp" \
  --from-literal=DEFAULT_ORG_SLUG="acme"
```

| Khóa                                                                    | Mục đích                                                                                                                                                                                                                                                                                                                                                                                                             |
| ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ADMIN_EMAIL`                                                           | Người dùng quản trị bootstrap. Upserted ở mỗi lần khởi động với tất cả các quyền và được bảo vệ khỏi xóa/chỉnh sửa quyền thông qua bảng điều khiển. Nếu không có nó, không có quản trị viên nào được gieo và đăng nhập đầu tiên là không thể.                                                                                                                                                                        |
| `ALLOWED_EMAILS`                                                        | Danh sách cho phép được phân tách bằng dấu phẩy. Hỗ trợ địa chỉ chính xác (`user@example.com`) và ký tự đại diện miền (`*@example.com`). Nếu không có nó, **không có người dùng nào có thể đăng nhập hoặc được tạo**.                                                                                                                                                                                                |
| `SMTP_HOST`, `SMTP_PORT`, `SMTP_USERNAME`, `SMTP_PASSWORD`, `SMTP_FROM` | Bộ chuyển tiếp SMTP để gửi mã OTP. Nếu `SMTP_HOST` không được đặt, các mã OTP sẽ được ghi vào stdout của máy chủ thay vì được gửi qua email (hữu ích cho các bài kiểm tra khởi động). Cung cấp tất cả các khóa SMTP cùng nhau để gửi email thực.                                                                                                                                                                     |
| `SMTP_TLS`                                                              | Một trong `starttls` (mặc định), `tls` hoặc `none`.                                                                                                                                                                                                                                                                                                                                                                  |
| `DEFAULT_ORG_NAME`, `DEFAULT_ORG_SLUG`                                  | Tùy chọn. Đặt cho tổ chức `default` được xây dựng sẵn tên hiển thị thân thiện và slug URL để nó sống ở ví dụ `/acme` thay vì `/default`. Được áp dụng chỉ **trên lần khởi động đầu tiên**; khi bạn đổi tên tổ chức bằng `agenteye-orgctl org rename` (xem §7.6) những cái này bị bỏ qua. Slug phải là 1–40 chữ cái thường alphanumerics có gạch ngang nội bộ duy nhất. Để trống cả hai để giữ `default` chung chung. |

> **Lưu trữ thông tin xác thực SMTP trong trình quản lý bí mật của bạn.**

**Kiểm tra nó:**

```bash theme={null}
kubectl get secret agenteye-auth -n agenteye \
  -o jsonpath='{.data}' | grep -o '"[A-Z_]*"' | sort -u
```

Kết quả dự kiến: các khóa bạn điền xuất hiện trong đầu ra.

***

### 2.6 Khóa cô lập tổ chức đa người thuê (tùy chọn)

Bỏ qua điều này đối với triển khai một người thuê; máy chủ chạy trên một tổ chức mặc định phát triển được xây dựng sẵn và phục vụ tổ chức `default` duy nhất một cách tốt. **Trước khi bạn tạo tổ chức thứ hai**, hãy đặt một `ORG_CH_SECRET` mạnh, ổn định: mật khẩu ClickHouse của mỗi tổ chức được dẫn xuất dưới dạng `HMAC(ORG_CH_SECRET, org_id)`, vì vậy mặc định phát triển được biết công khai sẽ mang lại thông tin xác thực theo tổ chức được dẫn xuất công khai. Lệnh `agenteye-orgctl org create` (xem [§7.6 Cấp phát các tổ chức](#76-provision-organizations-multi-tenant)) từ chối chạy trong khi máy chủ vẫn ở mặc định phát triển được xây dựng sẵn.

```bash theme={null}
kubectl create secret generic agenteye-org-ch-secret \
  --namespace agenteye \
  --from-literal=ORG_CH_SECRET="$(openssl rand -base64 48)"

# Khởi động lại máy chủ để nó nhận giá trị mới.
kubectl -n agenteye rollout restart deployment/server
```

Máy chủ đọc điều này qua một **optional** `secretKeyRef`, vì vậy một cụm một người thuê không bao giờ tạo nó vẫn khởi động bình thường. Giữ giá trị **ổn định và giống nhau trên tất cả các bản sao**; quay nó vô hiệu hóa mật khẩu ClickHouse được dẫn xuất của mỗi tổ chức cho đến khi khởi động lại khỏi mục tiêu cấu hình lại các người dùng (một khởi động động với giá trị nhất quán ở khắp nơi chữa nó). Xem `deploy/base/server/secret.example.yaml`.

> **Lưu trữ `ORG_CH_SECRET` trong trình quản lý bí mật của bạn và đừng quay nó một cách tùy tiện.**

***

### 2.7 Xác minh tất cả các bí mật

```bash theme={null}
kubectl get secrets -n agenteye -o custom-columns='NAME:.metadata.name,TYPE:.type'
```

Đầu ra dự kiến (trong số bất kỳ bí mật mặc định nào):

```
NAME                    TYPE
agenteye-admin-key      Opaque
agenteye-auth           Opaque
agenteye-image-pull     kubernetes.io/dockerconfigjson
agenteye-postgres       Opaque
agenteye-org-ch-secret  Opaque   # chỉ nếu bạn hoàn thành §2.6 (đa người thuê)
```

Bốn bí mật cốt lõi (`agenteye-admin-key`, `agenteye-auth`, `agenteye-image-pull`, `agenteye-postgres`) phải có mặt trước khi tiếp tục. `agenteye-org-ch-secret` chỉ bắt buộc đối với triển khai đa người thuê (xem §2.6).

***

## Giai đoạn 3 -- Triển khai ứng dụng (\~5 phút)

### 3.1 Cấu hình các tên máy chủ công khai

cert-manager cần các tên máy chủ tiêu thụ và bảng điều khiển trước khi nó có thể yêu cầu chứng chỉ Let's Encrypt của họ. Sao chép mẫu và đặt cả hai:

```bash theme={null}
cp base/certificates/domain.env.example base/certificates/domain.env
# Chỉnh sửa base/certificates/domain.env và đặt:
#   INGEST_DOMAIN=ingest.your-company.example      (phân giải thành LB Traefik công khai)
#   DASHBOARD_DOMAIN=agenteye.your-company.example (phân giải thành LB Traefik bảng điều khiển)
```

`domain.env` được gitignored; nó ở địa phương cho mỗi triển khai. Xây dựng kustomize không thành công ồn ào nếu khóa nào bị thiếu.

> **DNS phải phân giải trước tiên.** Bạn không phải chỉ định DNS tại LB ngay bây giờ (chúng không tồn tại cho đến khi Giai đoạn 1.2 hoàn thành), nhưng cấp phát ACME ở bước 3.2 sẽ thử lại cho đến khi mỗi tên máy chủ phân giải thành LoadBalancer của nó. Bạn có thể đặt DNS ngay bây giờ (sử dụng tên máy chủ LB được nắm bắt trong Giai đoạn 1.4) hoặc tiếp tục và thêm các bản ghi trong Giai đoạn 4.

***

### 3.2 Áp dụng bản kê khai

Áp dụng cơ sở trực tiếp để cài đặt mới hoặc lớp phủ nếu bạn đã cắt một cho môi trường này (lớp phủ chỉ ghim các thẻ hình ảnh, biến env và giới hạn tài nguyên; chúng kế thừa chứng chỉ và định tuyến cơ sở):

```bash theme={null}
kubectl apply -k base/
# hoặc
kubectl apply -k overlays/<your-env>/
```

Lớp phủ bao gồm cơ sở tự động; áp dụng một, không phải cả hai.

***

### 3.3 Chờ các pod

```bash theme={null}
kubectl wait --for=condition=Ready pod -l 'app in (server,dashboard,postgres,clickhouse)' \
  -n agenteye --timeout=180s
```

Chờ được phạm vi cho các pod mặt phẳng dữ liệu cốt lõi. Pod `agent` (trợ lý AI) và `redis` tùy chọn xuất hiện bên cạnh chúng; trợ lý ở vô hoạt cho đến khi bạn cung cấp điểm cuối LLM của nó (xem [enterprise-docs/assistant.md](/vi/agenteye/assistant)), và Redis là bộ nhớ đệm tốt nhất, vì vậy không ai cần ở Sẵn sàng để nền tảng phục vụ lưu lượng truy cập.

**Kiểm tra nó:**

```bash theme={null}
kubectl get pods -n agenteye
```

Kết quả dự kiến (pod `agent` và `redis` tùy chọn cũng xuất hiện và đạt `Running`):

```
NAME                         READY   STATUS    RESTARTS   AGE
agent-xxxxxxxxxx-xxxxx       1/1     Running   0          ...
clickhouse-0                 1/1     Running   0          ...
dashboard-xxxxxxxxxx-xxxxx   1/1     Running   0          ...
dashboard-xxxxxxxxxx-xxxxx   1/1     Running   0          ...
postgres-0                   1/1     Running   0          ...
redis-0                      1/1     Running   0          ...
server-xxxxxxxxxx-xxxxx      1/1     Running   0          ...
server-xxxxxxxxxx-xxxxx      1/1     Running   0          ...
```

**Nếu nó không thành công:**

| Trạng thái Pod     | Nguyên nhân có thể                    | Lệnh gỡ lỗi                                                  |
| ------------------ | ------------------------------------- | ------------------------------------------------------------ |
| `ImagePullBackOff` | Bí mật kéo hình ảnh xấu hoặc PAT      | `kubectl describe pod <name> -n agenteye`                    |
| `CrashLoopBackOff` | Biến env xấu (ví dụ: DATABASE\_URL)   | `kubectl logs <name> -n agenteye`                            |
| `Pending`          | CPU/bộ nhớ không đủ hoặc không có nút | `kubectl describe pod <name> -n agenteye` (kiểm tra Sự kiện) |

***

### 3.4 Xác minh bộ lưu trữ

```bash theme={null}
kubectl get pvc -n agenteye
```

Kết quả dự kiến, cả hai có trạng thái `Bound`:

| PVC                            | Dung lượng | Lưng                                                |
| ------------------------------ | ---------- | --------------------------------------------------- |
| `postgres-data-postgres-0`     | `50Gi`     | Kho lưu trữ quan hệ/siêu dữ liệu PostgreSQL         |
| `clickhouse-data-clickhouse-0` | `100Gi`    | Kho lưu trữ phân tích sự kiện + đánh giá ClickHouse |

Một PVC `redis-data-redis-0` (1Gi) cũng xuất hiện cho bộ nhớ đệm tùy chọn.

**Nếu nó không thành công:** `Pending` có nghĩa là không có StorageClass nào có thể cấp phát khối lượng. Kiểm tra `kubectl get storageclass` và đảm bảo tồn tại một mặc định. Để sản xuất, lớp phủ khối lượng ClickHouse lên StorageClass SSD nhanh (ví dụ: gp3 trên AWS, pd-ssd trên GCP); thông lượng nén bị ảnh hưởng trên đĩa chậm.

***

### 3.5 Xác minh chứng chỉ

```bash theme={null}
kubectl get certificates -n agenteye
```

Kết quả dự kiến: 3 chứng chỉ, tất cả `Ready: True`:

| Tên             | Công cộng          | Mục đích                                                                  |
| --------------- | ------------------ | ------------------------------------------------------------------------- |
| `mtls-ca`       | `selfsigned`       | CA riêng để phát hành chứng chỉ máy khách mTLS (hiệu lực 10 năm)          |
| `ingest-tls`    | `letsencrypt-prod` | Chứng chỉ TLS công khai cho điểm cuối tiêu thụ (90 ngày, tự động gia hạn) |
| `dashboard-tls` | `letsencrypt-prod` | Chứng chỉ TLS công khai cho bảng điều khiển (90 ngày, tự động gia hạn)    |

**Nếu `ingest-tls` hoặc `dashboard-tls` không sẵn sàng:**

`kubectl describe certificate <name> -n agenteye` và đọc Sự kiện. Các nguyên nhân phổ biến:

* **DNS chưa chỉ đến LB.** Let's Encrypt phân giải tên máy chủ và nhấn cổng 80 để xác thực — `INGEST_DOMAIN` phải phân giải thành LB công khai, `DASHBOARD_DOMAIN` thành LB bảng điều khiển. Cho đến khi CNAME/Alias lan rộng, đơn đặt hàng vẫn `pending`. Khi DNS đúng, cert-manager thử lại tự động (không cần xóa Chứng chỉ).
* **Tên máy chủ không được thay thế.** Nếu `dnsNames` vẫn đọc `INGEST_DOMAIN_PLACEHOLDER` / `DASHBOARD_DOMAIN_PLACEHOLDER`, bạn đã bỏ qua bước 3.1 -- tạo `base/certificates/domain.env` và áp dụng lại.
* **Traefik bảng điều khiển không thể phục vụ thử thách** (`dashboard-tls` chỉ). Thực thể Traefik bảng điều khiển phải được cài đặt với tệp giá trị được đóng gói (Giai đoạn 1.2), điều này cho phép nhà cung cấp Ingress được xác định phạm vi phục vụ bộ giải quyết HTTP-01 của cert-manager. Một thực thể được cài đặt mà không có nó để thử thách không có tuyến đường và thứ tự `pending` mãi mãi.

**Nếu `mtls-ca` không sẵn sàng:** cert-manager chính nó không khỏe. Kiểm tra lại các pod cert-manager từ bước 1.1.

***

### 3.6 Xác minh CronJob

```bash theme={null}
kubectl get cronjobs -n agenteye
```

Kết quả dự kiến:

| Tên                  | Lịch           | Mục đích                                              |
| -------------------- | -------------- | ----------------------------------------------------- |
| `agenteye-backup`    | `0 3 * * *`    | Sao lưu Postgres + ClickHouse hàng ngày lúc 03:00 UTC |
| `cert-renewal-check` | `0 3,15 * * *` | Cảnh báo hết hạn chứng chỉ lúc 03:00 và 15:00 UTC     |

***

### 3.7 Xác minh máy chủ khởi động chính xác

```bash theme={null}
kubectl logs -n agenteye -l app=server --tail=20
```

**Kiểm tra nó:** Tìm dòng khởi động cho biết máy chủ đang nghe trên cổng 8080. Không nên có lỗi kết nối cơ sở dữ liệu (máy chủ yêu cầu cả PostgreSQL và ClickHouse có thể truy cập được trước khi nó báo cáo Sẵn sàng).

**Nếu nó không thành công:** Nguyên nhân phổ biến nhất là `POSTGRES_PASSWORD` chứa các ký tự không an toàn URL mà bảng `DATABASE_URL`. Xem [enterprise-docs/troubleshooting.md](/vi/agenteye/troubleshooting).

***

### 3.8 Xác minh bảng điều khiển được kết nối với máy chủ

```bash theme={null}
kubectl logs -n agenteye -l app=dashboard --tail=20
```

**Kiểm tra nó:** Tìm `Ready` trong đầu ra mà không có `ECONNREFUSED` hoặc các lỗi tương tự.

**Nếu nó không thành công:** Kiểm tra rằng Dịch vụ `server` tồn tại (`kubectl get svc server -n agenteye`) và `AGENTEYE_SERVER_URL` được đặt thành `http://server:8080` trong triển khai bảng điều khiển.

***

## Giai đoạn 4 -- Quyền truy cập mạng (\~5 phút)

### 4.1 Truy xuất địa chỉ LoadBalancer

```bash theme={null}
PUBLIC_IP=$(kubectl get svc -n traefik-public \
  -o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}')

INTERNAL_IP=$(kubectl get svc -n traefik-dashboard \
  -o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}')
```

> Trên AWS EKS, LoadBalancer trả về tên máy chủ thay vì IP. Thay thế `.ip` bằng `.hostname` trong các lệnh trên.

**Kiểm tra nó:**

```bash theme={null}
echo "Public  (ingest):    $PUBLIC_IP"
echo "Internal (dashboard): $INTERNAL_IP"
```

Cả hai phải không trống.

***

### 4.2 Trỏ DNS tại LoadBalancer

Tạo bản ghi DNS để các tên máy chủ từ `base/certificates/domain.env` phân giải thành LoadBalancer của họ — `INGEST_DOMAIN` thành LB Traefik **công khai**, `DASHBOARD_DOMAIN` thành LB Traefik **bảng điều khiển**:

* **AWS Route 53:** Bản ghi `A` có `Alias = Yes`, mục tiêu = tên máy chủ LB. Đừng sử dụng A bình thường → IP; IP ELB xoay.
* **Bất kỳ nhà cung cấp nào khác:** `CNAME` từ tên máy chủ đến tên máy chủ LB.

Xác minh:

```bash theme={null}
dig +short ingest.your-company.example
dig +short agenteye.your-company.example
```

Nên trả về cùng địa chỉ như `$PUBLIC_IP` và `$INTERNAL_IP` tương ứng (hoặc, trên EKS, phân giải thành cùng tên máy chủ `*.elb.amazonaws.com`).

Khi DNS phân giải, cert-manager hoàn thành các đơn đặt hàng ACME chưa hoàn thành từ Giai đoạn 3.5 trong vòng một phút. Chạy lại `kubectl get certificates -n agenteye` cho đến khi cả `ingest-tls` và `dashboard-tls` hiển thị `Ready: True`.

***

### 4.3 Tiếp cận điểm cuối tiêu thụ

Điểm cuối tiêu thụ công khai thực thi TLS lẫn nhau, vì vậy mỗi yêu cầu (bao gồm `/health`) phải trình bày chứng chỉ khách hàng. Bạn phát hành chứng chỉ khách hàng đầu tiên của bạn trong Giai đoạn 5; nếu bạn đã có cái, hãy xác minh khả năng truy cập ngay bây giờ:

```bash theme={null}
curl -s --cert issued/<cluster-name>/client.crt \
        --key issued/<cluster-name>/client.key \
        https://ingest.your-company.example/health
```

Kết quả dự kiến: `{"status":"ok"}`. Không cần `-k` -- chứng chỉ máy chủ chuỗi đến CA công khai cho `INGEST_DOMAIN`, vì vậy nó xác thực so với kho tin cậy hệ thống. Tiếp cận điểm cuối tiêu thụ bằng tên máy chủ `INGEST_DOMAIN` của nó (khớp với chứng chỉ phát hành), không phải bởi IP/tên máy chủ LB thô.

Điểm cuối bảng điều khiển được phục vụ trên `DASHBOARD_DOMAIN` có chứng chỉ được tin cậy công khai và không phải mTLS, vì vậy không cần `-k` và không cần chứng chỉ khách hàng:

```bash theme={null}
curl -s https://agenteye.your-company.example/ -o /dev/null -w '%{http_code}\n'
```

Tiếp cận bảng điều khiển bằng tên máy chủ của nó, không phải địa chỉ LB thô — chứng chỉ bị ràng buộc với `DASHBOARD_DOMAIN`, vì vậy địa chỉ thô hiển thị sự không khớp về tên chứng chỉ.

**Nếu nó không thành công:** Nếu `curl` treo, kiểm tra rằng LB có thể truy cập được từ máy của bạn (VPN, nhóm bảo mật, quy tắc tường lửa). Lỗi bắt tay `certificate required` trên tên máy chủ tiêu thụ có nghĩa là không có chứng chỉ khách hàng được trình bày; hãy hoàn thành Giai đoạn 5 trước. Lỗi xác thực TLS trên tên máy chủ tiêu thụ có nghĩa là chứng chỉ máy chủ chưa hoàn thành phát hành; quay lại Giai đoạn 3.5 và giải quyết vấn đề ở đó.

***

## Giai đoạn 5 -- Phát hành chứng chỉ khách hàng mTLS (\~10 phút trên cụm)

Trạm thu thập xác thực bằng **hai yếu tố**: chứng chỉ khách hàng (lớp vận tải, chứng minh yêu cầu đến từ một cụm được ủy quyền) và khóa API (lớp ứng dụng, chứng minh yêu cầu từ trạm thu thập có quyền `events:add`). Khóa rò rỉ vô dụng mà không có cert; cert bị đánh cắp vô dụng mà không có khóa hợp lệ.

### 5.1 Phát hành chứng chỉ

Mỗi cụm chạy trạm thu thập cần chứng chỉ khách hàng riêng của nó. Từ thư mục bản kê khai:

```bash theme={null}
cd base/certificates/client-certs
./issue-client-cert.sh <cluster-name>
```

Thay thế `<cluster-name>` bằng một định danh có ý nghĩa (ví dụ: `us-east-1-prod`, `staging`).

**Kiểm tra nó:** Tập lệnh in `==> Done!` và liệt kê các tệp đầu ra.

```bash theme={null}
kubectl get certificate mtls-client-<cluster-name> -n agenteye
```

Kết quả dự kiến: `Ready: True`.

Tệp đầu ra trong `issued/<cluster-name>/`:

| Tệp                          | Mục đích                                                 |
| ---------------------------- | -------------------------------------------------------- |
| `client.crt`                 | Chứng chỉ khách hàng (hiệu lực 90 ngày)                  |
| `client.key`                 | Khóa riêng máy khách                                     |
| `ca.crt`                     | Chứng chỉ CA để xác minh máy chủ                         |
| `collector-mtls-secret.yaml` | Bí mật Kubernetes sẵn sàng áp dụng cho cụm trạm thu thập |

***

### 5.1b Phương tiện thay thế: AWS Secrets Manager

Nếu người tiêu dùng cert là một Pod Kubernetes cần `client.crt` và `client.key` trên đĩa -- trường hợp điển hình khi bạn chạy trạm thu thập agenteye-collector dưới dạng sidecar trong pod ứng dụng của bạn -- đẩy bó cert vào AWS Secrets Manager. Pod ứng dụng sau đó gắn nó qua [Secrets Store CSI Driver](https://secrets-store-csi-driver.sigs.k8s.io/) với IRSA, và quay một vòng chứng chỉ hoàn toàn tự động.

```bash theme={null}
cd base/certificates/client-certs
export AWS_REGION=us-east-1     # khu vực nơi khối lượng công việc của bạn chạy
./issue-client-cert.sh <cluster-name> --save-to aws-secrets-manager
```

Khi chạy lại (gia hạn), tập lệnh gọi `PutSecretValue` trên cùng một bí mật, vì vậy ARN và tên giữ ổn định. CSI Driver chọn phiên bản mới trên bộ xoay tiếp theo của nó và viết lại các tệp bên trong pod.

**Điều kiện tiên quyết:**

* `aws` CLI v2 được xác thực cho tài khoản AWS của bạn.
* `jq` cài đặt.
* Biến môi trường `AWS_REGION` được đặt.
* Quyền IAM trên danh tính người gọi của bạn (phạm vi `Resource` thành `arn:aws:secretsmanager:<region>:<account>:secret:agenteye/mtls-client/*`):
  * `secretsmanager:CreateSecret`
  * `secretsmanager:DescribeSecret`
  * `secretsmanager:PutSecretValue`
  * `secretsmanager:TagResource`

**Tập lệnh làm gì ở chế độ này:**

| Bước | Hành động                                                                                                                                                                                                                                                        |
| ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1    | Phát hành / tái trích xuất cert qua cert-manager (giống như chế độ mặc định).                                                                                                                                                                                    |
| 2    | Gọi `DescribeSecret` trên `agenteye/mtls-client/<cluster-name>` để quyết định tạo vs cập nhật.                                                                                                                                                                   |
| 3    | Trên lần chạy đầu tiên: `CreateSecret` với tải trọng JSON ba khóa (`client.crt`, `client.key`, `ca.crt`), được gắn thẻ `AgentEyeCluster=<cluster-name>`. Trên các lần chạy tiếp theo: `PutSecretValue` để xuất bản phiên bản mới; thẻ làm mới qua `TagResource`. |
| 4    | Xóa `issued/<cluster-name>/` chỉ sau khi tải lên thành công. Khi có lỗi, thư mục được bảo tồn để bạn thử lại.                                                                                                                                                    |

**Nếu bí mật được lên lịch xóa**, tập lệnh không thành công với lỗi rõ ràng yêu cầu bạn chạy `aws secretsmanager restore-secret --secret-id agenteye/mtls-client/<cluster-name>` trước khi thử lại.

Để dây toàn bộ pod (SecretProviderClass, thiết lập IRSA, hành vi quay một vòng, gỡ lỗi) xem [enterprise-docs/single-pod-deployment.md](/vi/agenteye/single-pod-deployment).

***

### 5.2 Xác minh chứng chỉ hoạt động

Kiểm tra chứng chỉ phát hành so với mTLS ingress:

```bash theme={null}
curl -sk --cert issued/<cluster-name>/client.crt \
     --key issued/<cluster-name>/client.key \
     https://${PUBLIC_IP}/health
```

Kết quả dự kiến: `{"status":"ok"}`

**Nếu nó không thành công:**

| Lỗi                    | Nguyên nhân                                   | Khắc phục                                                                                               |
| ---------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `certificate required` | Cert không được trình bày                     | Kiểm tra đường dẫn tệp trong lệnh `curl`                                                                |
| `bad certificate`      | Không khớp CA                                 | Xác minh `mtls-ca-issuer` phát hành cert: `kubectl describe certificate mtls-client-<name> -n agenteye` |
| `connection refused`   | Tên máy chủ hoặc LB sai không có thể truy cập | Kiểm tra `/etc/hosts` hoặc DNS                                                                          |

***

### 5.3 Gửi tới cụm trạm thu thập

Gửi `collector-mtls-secret.yaml` cho đội vận hành cụm trạm thu thập. Họ áp dụng nó:

```bash theme={null}
kubectl apply -f collector-mtls-secret.yaml -n <collector-namespace>
```
