---
id: kubernetes-config-secrets
title: "Biến môi trường và Secret"
shortTitle: "Env & Secret"
track: kubernetes
trackTitle: "Kubernetes"
order: 6
level: core
status: published
estimatedMinutes: 40
prerequisites:
  - kubernetes-volumes
next: kubernetes-init-sidecar
reviewedAgainst: 2026-10-15
sources:
  - title: "KodeKloud Engineer — 100 Days of DevOps"
    url: https://engineer.kodekloud.com/practice
  - title: "Define environment variables for a container"
    url: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/
  - title: "Secrets"
    url: https://kubernetes.io/docs/concepts/configuration/secret/
  - title: "ConfigMaps"
    url: https://kubernetes.io/docs/concepts/configuration/configmap/
---

# Biến môi trường và Secret

App đọc `DB_HOST`, mật khẩu, token. Ghi thẳng vào image thì ai pull cũng thấy. Ghi vào YAML rồi commit thì Git giữ mãi. Kubernetes đưa giá trị lúc Pod chạy: `env`, `envFrom`, Secret, ConfigMap.

## Để làm gì

Bạn set biến bằng `env`, lấy cả khóa từ Secret/ConfigMap bằng `envFrom`, tạo Secret bằng `kubectl create secret generic`, và biết Secret trong etcd mặc định không mã hóa.

## Gồm những gì

- `env.name` + `env.value`.
- `env.valueFrom.secretKeyRef` / `configMapKeyRef`.
- `envFrom` lấy hết key thành biến.
- `kubectl create secret generic` `--from-literal` / `--from-file`.
- ConfigMap cho config không mật.

Không dựng Vault hay Sealed Secrets. Ngày 63–67 (nhiều app: web + Redis + MySQL) chỉ là cùng các object này đứng cạnh nhau — không chép tên app lab.

## Dùng khi nào

Image cần biến lúc start. Mật khẩu, token: Secret. Hostname, cờ feature: ConfigMap hoặc `env` thường.

Đừng `ENV PASSWORD=` trong Dockerfile. Đừng commit `--from-literal` trong script đã push. Ai `get secret -o yaml` vẫn đọc được nếu có quyền API.

## Kiến thức cốt lõi

Docs: trường `env` hoặc `envFrom` trong container. `env` đặt từng biến. `envFrom` đổ cả ConfigMap hoặc Secret; có thể thêm prefix.

`env` / `envFrom` **đè** biến cùng tên đã có trong image.

Biến tham chiếu nhau bằng `$(TÊN)` trong cùng danh sách `env`; biến bị dùng phải đứng trước. Đừng vòng.

```yaml
env:
  - name: GREETING
    value: "hello"
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: db-pass
        key: password
envFrom:
  - configMapRef:
      name: app-config
```

Secret giống ConfigMap về cách gắn, khác mục đích: dữ liệu nhạy cảm. Tạo độc lập với Pod. Dùng được: biến môi trường, volume file, imagePullSecret.

Cảnh báo docs: Secret **mặc định lưu không mã hóa** trong etcd. Ai có quyền API (kể cả tạo Pod trong namespace) có thể đọc Secret namespace đó. Production: encryption at rest, RBAC hẹp, cân nhắc store ngoài cluster.

`kubectl create secret generic db-pass --from-literal=password=change-me`

`--from-file=key=/path` đọc nội dung file. YAML: `stringData` viết chữ thường, API đổi thành `data` base64. `kubectl get secret db-pass -o yaml` ra `data` đã encode — `echo ... | base64 -d` ra plaintext. Đó không phải mã hóa.

ConfigMap: `kubectl create configmap app-config --from-literal=DB_HOST=mysql-svc`. Cùng kiểu `env` / `envFrom` / volume. Volume ConfigMap luôn readOnly.

Đổi Secret/ConfigMap **không** tự restart Pod đang chạy. Cần `rollout restart` hoặc đổi template. Volume file có lúc được cập nhật; `subPath` thì không — docs ConfigMap/Secret ghi rõ.

Nhiều Deployment (web, Redis, MySQL): mỗi cái một Service. App gọi `DB_HOST=mysql-svc`, không `localhost`. Mật khẩu MySQL và app phải cùng một Secret key. PVC cho MySQL, không cho web stateless — đã nói ở bài volume.

## Lệnh và code mẫu

```bash
kubectl create secret generic db-pass --from-literal=password=change-me --dry-run=client -o yaml
kubectl create secret generic db-pass --from-literal=password=change-me
kubectl get secret db-pass
```

Pod in biến (image `busybox`, lab):

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: env-demo
spec:
  restartPolicy: Never
  containers:
    - name: box
      image: busybox:1.36
      command: ["sh", "-c", "printenv DEMO_GREETING && printenv DB_PASSWORD"]
      env:
        - name: DEMO_GREETING
          value: "hello from env"
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-pass
              key: password
```

```bash
kubectl apply -f env-demo.yaml
kubectl logs env-demo
kubectl exec env-demo -- printenv DEMO_GREETING
```

Gỡ:

```bash
kubectl delete pod env-demo
kubectl delete secret db-pass
```

## Thực hành

1. `create secret generic` với `--dry-run=client -o yaml`. Nhìn `data` base64, không phải chữ bạn gõ.
2. Apply Secret rồi Pod trên. `logs` phải in greeting và password.
3. `kubectl exec … -- printenv`. So với biến trong image: key trùng thì giá trị YAML thắng.
4. Thiếu Secret, apply Pod: Events `CreateContainerConfigError` / secret not found. Tạo Secret, xóa Pod, tạo lại.

## Kết quả mong đợi

- Dry-run Secret có `data` encode.
- `printenv` ra đúng hai biến.
- Pod tạo trước Secret thì không Running cho đến khi Secret có và Pod được tạo lại.

## Checklist

- [ ] `env` từng key; `envFrom` cả object.
- [ ] Secret: `--from-literal` hoặc `stringData`. `get -o yaml` là base64, không phải mã hóa.
- [ ] etcd mặc định không encrypt Secret.
- [ ] Đổi Secret rồi `rollout restart` nếu Pod đang chạy.
- [ ] App gọi tên Service, không `localhost`.

## Thẻ ôn

### kubernetes-config-secrets-env
kind: command
cue: `env` từng biến. `valueFrom.secretKeyRef` lấy một key. `envFrom` đổ cả Secret/ConfigMap.

Q: Cần `DB_PASSWORD` từ Secret `db-pass` key `password`, và `DEMO=ok` viết thẳng. Khối `env` viết thế nào?

A: Một phần tử `name: DEMO` + `value: ok`. Một phần tử `name: DB_PASSWORD` + `valueFrom.secretKeyRef.name/key`. Đừng để password trong `value:`. `envFrom.secretRef` nếu muốn mọi key của Secret thành biến — dễ lộ key không cần.

### kubernetes-config-secrets-create
kind: command
cue: `kubectl create secret generic TÊN --from-literal=key=value`. `data` trên API là base64.

Q: Tạo Secret `db-pass` với key `password`. Lệnh nào, và `get -o yaml` thấy chữ `change-me` không?

A: `kubectl create secret generic db-pass --from-literal=password=change-me`. Output YAML có `data.password` base64. Decode ra plaintext. Ai `get secret` đủ quyền là đọc được.

### kubernetes-config-secrets-etcd
kind: trap
cue: Secret mặc định không mã hóa trong etcd. Quyền tạo Pod trong namespace đọc được Secret namespace đó.

Q: "Đã chuyển mật khẩu sang Secret, vậy Git và etcd đều an toàn." Câu nào sai?

A: etcd chưa chắc. Docs: mặc định không encrypt at rest. Git an toàn hơn nếu không commit Secret. Cluster vẫn cần encryption at rest + RBAC. `create secret` trên máy rồi dán literal vào lịch sử shell cũng là chỗ lộ.

### kubernetes-config-secrets-override
kind: default
cue: `env` / `envFrom` đè biến cùng tên trong image.

Q: Image đã `ENV DEMO_GREETING=from-image`. Pod khai `env.DEMO_GREETING=from-pod`. `printenv` ra gì?

A: `from-pod`. Docs: biến từ `env`/`envFrom` thắng biến image.

### kubernetes-config-secrets-restart
kind: trap
cue: Sửa Secret không restart Pod. Cần đổi template hoặc `rollout restart`.

Q: Đổi password trong Secret, Deployment không đổi YAML. App còn password cũ. Vì sao, làm gì?

A: Process đã start với env lúc tạo container. Secret mới không đẩy vào process đang chạy. `kubectl rollout restart deployment/…` hoặc sửa annotation/template để tạo Pod mới.

## Lỗi thường gặp

**`secretKeyRef.key` không có trong Secret.** Container không tạo, `CreateContainerConfigError`.

**Commit file `--from-literal`.** Xoay mật khẩu. Xóa commit không gỡ remote.

**`localhost` cho MySQL** trong cùng namespace. `localhost` là Pod web. Tên Service MySQL mới đúng.

**Coi base64 là mã hóa.** Ai có YAML là decode được.

## Nguồn

1. [KodeKloud Engineer — 100 Days of DevOps](https://engineer.kodekloud.com/practice) — đề cương ngày Print Environment Variables, Manage Secrets, và các ngày deploy app + Redis + MySQL. Trang practice cần đăng nhập. Bài này viết lại lệnh, không chép lab.
2. [Define environment variables](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) — `env`, `envFrom`, đè biến image, `$(TÊN)`.
3. [Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) — cách dùng, etcd không mã hóa mặc định, RBAC.
4. [ConfigMaps](https://kubernetes.io/docs/concepts/configuration/configmap/) — config không mật, volume readOnly.

## Tiếp theo

[Init container và sidecar](./13-init-sidecar.md)
