Giải thích về các biến môi trường: .env Tệp, Bí mật và Cấu hình đã được thực hiện đúng
Các biến môi trường cho phép cùng một mã chạy trên máy tính xách tay, trong CI và trong sản xuất với các cài đặt khác nhau. Đây là cách chúng thực sự hoạt động, với những bất ngờ mà chúng tôi gặp phải khi thử nghiệm chúng.
- Biến môi trường là các cài đặt khóa-giá trị mà một tiến trình nhận được từ môi trường của nó.
- Họ loại bỏ cấu hình, chẳng hạn như cổng và URL cơ sở dữ liệu, khỏi mã của bạn.
- Các giá trị luôn là chuỗi, vì vậy “false” là đúng trừ khi bạn phân tích cú pháp.
- Không bao giờ cam kết các tệp
.envcó bí mật thực sự và xác thực các cài đặt bắt buộc khi ứng dụng khởi động.
Ứng dụng của bạn cần có URL cơ sở dữ liệu. Trên máy tính xách tay của bạn, đó là cơ sở dữ liệu cục bộ, trong CI là cơ sở dữ liệu dùng một lần và trong sản xuất là một cụm được quản lý. Mã hóa cứng bất kỳ trong số chúng có nghĩa là thay đổi mã cho từng môi trường. Biến môi trường giải quyết vấn đề này. Mã đọc DATABASE_URL và mỗi môi trường cung cấp giá trị riêng.
Ý tưởng này là trọng tâm của phương pháp Ứng dụng 12 yếu tố, trong đó cho biết cấu hình khác nhau giữa các lần triển khai sẽ thuộc về môi trường. Chúng tôi đã thử nghiệm hành vi bên dưới với Python 3.13 trên Windows. Mọi đầu ra được hiển thị là từ lần chạy đó.
Biến môi trường là gì?
Mọi tiến trình đang chạy đều có một tập hợp các giá trị văn bản được đặt tên gọi là môi trường của nó. Các ví dụ quen thuộc là PATH, cho shell biết nơi tìm chương trình và HOME hoặc USERPROFILE. Ứng dụng của riêng bạn có thể sử dụng cơ chế tương tự cho các cài đặt như APP_PORT, LOG_LEVEL hoặc DATABASE_URL.
Đặt biến môi trường
| Vỏ | Đối với phiên này | Đối với một lệnh |
|---|---|---|
| Bash / zsh | xuất APP_PORT=8080 |
APP_PORT=8080 ứng dụng python |
| PowerShell | $env:APP_PORT = "8080" |
Đặt nó, chạy, sau đó xóa nó |
| Lệnh Windows | đặt APP_PORT=8080 |
Không được tích hợp sẵn |
Những điều này chỉ kéo dài cho phiên cuối cùng hiện tại. Để biến chúng thành vĩnh viễn, hãy thêm chúng vào cấu hình shell chẳng hạn như ~/.bashrc hoặc trên Windows, hãy sử dụng cài đặt môi trường hệ thống hoặc setx. Đối với các ứng dụng, tốt hơn là cung cấp chúng tại thời điểm triển khai thay vì cài đặt chúng trên toàn hệ thống.
Đọc chúng trong mã
import os
port = os.environ.get("APP_PORT", "8000") # default if missing
print(port) # 8000
db = os.environ["DATABASE_URL_MISSING_DEMO"]
# KeyError: 'DATABASE_URL_MISSING_DEMO'
Sử dụng .get() với giá trị mặc định cho cài đặt tùy chọn. Sử dụng dấu ngoặc vuông tăng KeyError cho các cài đặt mà ứng dụng không thể hoạt động nếu không có. Các ngôn ngữ khác cũng tương tự: process.env.APP_PORT trong Node.js, System.getenv("APP_PORT") trong Java và os.Getenv("APP_PORT") trong Go.
Mọi thứ đều là một chuỗi
Điều này thu hút mọi người ở mọi ngôn ngữ. Chúng tôi đặt một cổng và sau đó thêm “1” vào đó:
os.environ["APP_PORT"] = "8080"
port = os.environ["APP_PORT"]
print(type(port).__name__, repr(port), port + "1")
# str '8080' 80801
os.environ["APP_PORT"] = 8080
# TypeError: str expected, not int
Giá trị trả về dưới dạng văn bản và phép cộng tạo ra 80801, không phải 8081. Chuyển đổi rõ ràng, như trong int(os.environ["APP_PORT"]). Booleans tệ hơn:
os.environ["DEBUG"] = "false"
print(bool(os.environ["DEBUG"])) # True
Bất kỳ chuỗi nào không trống đều là chuỗi trung thực trong Python, vì vậy "false" trở thành True. Chế độ gỡ lỗi sẽ được bật trong quá trình sản xuất. Phân tích các boolean một cách có chủ ý:
def env_bool(name, default=False):
value = os.environ.get(name)
if value is None:
return default
return value.strip().lower() in {"1", "true", "yes", "on"}
env_bool("DEBUG") # False
env_bool("NOT_SET", default=True) # True
Các tiến trình kế thừa chúng như thế nào
Tiến trình con nhận được bản sao của môi trường cha của nó. Chúng tôi đã bắt đầu các tiến trình Python con để thấy điều này:
child sees: 8080 inherited # default: full copy of the parent's environment
child sees: 9000 None # given a custom env: only what we passed
parent sees child var: None # a child's changes never flow back
Sau đây là ba bài học thực tế. Các biến bạn xuất sẽ hiển thị với mọi chương trình bạn khởi chạy, bao gồm cả các tập lệnh bạn không viết, điều này quan trọng đối với bí mật. Việc chuyển qua một môi trường tùy chỉnh sẽ thay thế toàn bộ nội dung, vì vậy hãy bao gồm mọi thứ mà trẻ cần. Và một tập lệnh không thể thay đổi các biến của shell mẹ của nó, đó là lý do tại sao các công cụ yêu cầu bạn source một tệp thay vì chạy nó.
tập tin .env
Việc nhập nhiều biến bằng tay trở nên tẻ nhạt, vì vậy hầu hết các dự án đều giữ các giá trị phát triển cục bộ trong tệp .env:
# local development only
APP_PORT=8080
DATABASE_URL="postgres://dev:devonly@localhost:5432/shop"
FEATURE_SEARCH=true
Các thư viện như python-dotenv và dotenv cho Node.js, tải tệp này khi khởi động. Node.js 20.6 trở lên cũng có thể tải nguyên bản bằng --env-file. Để xem ý tưởng, chúng tôi đã viết một trình phân tích cú pháp nhỏ, biến tệp ở trên thành:
{'APP_PORT': '8080',
'DATABASE_URL': 'postgres://dev:devonly@localhost:5432/shop',
'FEATURE_SEARCH': 'true'}
Sử dụng một thư viện thực sự trong thực tế. Nó xử lý trích dẫn, thoát, nhận xét và các giá trị nhiều dòng mà phiên bản 10 dòng của chúng tôi bỏ qua. Theo mặc định, hầu hết các trình tải dotenv không ghi đè các biến đã được đặt, vì vậy các giá trị môi trường thực sẽ chiếm ưu thế trong tệp. Đó thường là những gì bạn muốn.
Mẹo: Thêm .env vào .gitignore trước lần cam kết đầu tiên của bạn và thay vào đó, hãy cam kết .env.example với mọi tên biến và giá trị giữ chỗ an toàn. Các nhà phát triển mới sao chép nó và điền vào các giá trị của riêng họ.
Xác thực cấu hình khi khởi động
Một biến bị thiếu sẽ dừng ứng dụng ngay lập tức với thông báo rõ ràng, không gây ra lỗi khó hiểu một giờ sau đó. Chúng tôi đã kiểm tra các cài đặt cần thiết khi tải:
REQUIRED = ["DATABASE_URL", "SECRET_KEY"]
def check_required(env):
missing = [k for k in REQUIRED if not env.get(k)]
if missing:
raise SystemExit(f"Missing required settings: {', '.join(missing)}")
# Missing required settings: SECRET_KEY
Thư viện cấu hình đã gõ đi xa hơn. Cài đặt Pydantic trong Python, zod hoặc envalid trong Node.js và thuộc tính cấu hình của Spring trong Java đều chuyển đổi các loại và báo cáo mọi vấn đề cùng một lúc.
Biến môi trường và bí mật
Biến môi trường là cách phổ biến để truyền các bí mật như khóa API tới một ứng dụng và chúng tốt hơn nhiều so với việc mã hóa cứng chúng. Họ có những điểm yếu cần biết về:
- Các tiến trình con kế thừa chúng, như thử nghiệm của chúng tôi đã cho thấy.
- Chúng có thể xuất hiện trong các báo cáo sự cố, trang gỡ lỗi và nhật ký hủy bỏ môi trường.
- Trên một số hệ thống, các quy trình khác do cùng một người dùng điều hành có thể đọc chúng.
Để sản xuất, nhiều nhóm giữ bí mật trong một trình quản lý chuyên dụng, chẳng hạn như HashiCorp Vault, AWS Secrets Manager, Azure Key Vault hoặc Google Secret Manager và đưa chúng vào thời gian chạy. Một số thích gắn các bí mật dưới dạng tệp thay vì biến. Dù bằng cách nào, đừng bao giờ in cấu hình trong nhật ký và xoay bất kỳ bí mật nào đã từng được cam kết với Git, ngay cả sau khi bạn xóa nó khỏi lịch sử.
Trong Docker và CI
# Docker (not run for this article)
docker run -e APP_PORT=8080 --env-file .env.production myapp
# GitHub Actions (not run for this article)
env:
APP_PORT: 8080
API_KEY: ${{ secrets.API_KEY }}
Nền tảng CI lưu trữ các bí mật được mã hóa và che giấu chúng trong nhật ký. Tránh đưa các bí mật vào hình ảnh Docker bằng ENV trong Dockerfile vì chúng vẫn có thể đọc được trong các lớp hình ảnh. Xem hướng dẫn Docker Compose của chúng tôi và Hướng dẫn CI/CD GitHub Actions để biết cách thiết lập hoàn chỉnh.
Các phương pháp hay nhất
- Sử dụng tên viết hoa, rõ ràng có tiền tố, chẳng hạn như
SHOP_DB_URL. - Đọc tất cả cấu hình trong một mô-đun, chuyển đổi loại ở đó và xác thực khi khởi động.
- Chỉ cung cấp các giá trị mặc định an toàn cho các cài đặt không nhạy cảm.
- Giữ
.envkhỏi Git và duy trì.env.example. - Sử dụng trình quản lý bí mật trong sản xuất cho các giá trị nhạy cảm.
- Ghi lại mọi biến, loại của nó và giá trị ví dụ.
Gỡ lỗi các vấn đề về biến môi trường
Khi một ứng dụng hoạt động như thể thiếu một cài đặt nào đó, hãy thực hiện các bước kiểm tra này theo thứ tự:
- Nó có được đặt đúng chỗ không? Một biến được xuất trong một tab thiết bị đầu cuối không tồn tại trong một tab khác hoặc trong một dịch vụ do hệ thống khởi động.
- Ứng dụng đã được khởi động lại chưa? Một quy trình đang chạy sẽ giữ nguyên môi trường mà nó đã bắt đầu. Thay đổi một biến sau đó không có hiệu lực cho đến khi khởi động lại.
- Tên có chính xác không? Tên có phân biệt chữ hoa chữ thường trên Linux và macOS, vì vậy
App_PortvàAPP_PORTlà các biến khác nhau. Windows đối xử với chúng như nhau. - Tệp .env có ghi đè hay bị bỏ qua không? Kiểm tra xem trình tải của bạn có ghi đè các giá trị hiện có hay không và tệp nào thực sự đọc.
- Có dấu ngoặc kép hoặc dấu cách đi lạc không? Một giá trị được sao chép có dấu cách ở cuối hoặc dấu ngoặc kép bổ sung có thể không phân tích được hoặc không khớp theo những cách khó phát hiện.
Để xem quy trình thực sự nhận được gì, hãy in tên biến, không bao giờ in giá trị bí mật. Trên Linux và macOS, printenv liệt kê môi trường của shell hiện tại và trong PowerShell Get-ChildItem Env: cũng làm như vậy. Bên trong một vùng chứa, chạy env đến docker exec hiển thị chế độ xem của vùng chứa, thường khác với chế độ xem của trình bao của bạn.
Mẹo cuối cùng: ghi lại một dòng khi khởi động liệt kê các cài đặt tùy chọn nào đang sử dụng giá trị mặc định của chúng. Nó không tốn kém gì và nó trả lời hầu hết câu hỏi “tại sao nó lại làm như vậy?” câu hỏi ngay lập tức.
Lời kết
Các biến môi trường rất đơn giản nhưng chúng ẩn chứa một số bẫy: mọi thứ đều là một chuỗi, trẻ em kế thừa chúng và các bí mật có thể rò rỉ qua chúng. Hãy coi chúng như một đầu vào cho ứng dụng của bạn. Đọc chúng ở một nơi, chuyển đổi và xác thực chúng sớm, đồng thời giữ bí mật thực sự khỏi các tệp được cam kết.
Câu hỏi thường gặp
Biến môi trường là gì?
Đó là một giá trị văn bản được đặt tên mà một tiến trình nhận được từ môi trường của nó, chẳng hạn như APP_PORT=8080. Các ứng dụng sử dụng chúng cho các cài đặt thay đổi giữa các môi trường.
Tệp .env là gì?
Tệp văn bản gồm các dòng KEY=value, được tải khi khởi động bởi thư viện như python-dotenv. Nó được sử dụng chủ yếu để phát triển địa phương và không nên cam kết với những bí mật thực sự.
Tại sao biến môi trường của tôi lại là một chuỗi?
Hệ điều hành lưu trữ các giá trị môi trường dưới dạng văn bản. Chuyển đổi các số bằng int() và phân tích các boolean một cách rõ ràng, vì chuỗi “false” được tính là đúng.
Các biến môi trường có an toàn cho bí mật không?
Chúng tốt hơn mã hóa cứng, nhưng các tiến trình con kế thừa chúng và nhật ký có thể làm lộ chúng. Hệ thống sản xuất thường sử dụng một trình quản lý bí mật chuyên dụng.
Tập lệnh có thể thay đổi các biến của trình bao mẹ của nó không?
Không. Tiến trình con nhận được một bản sao của môi trường, do đó những thay đổi của nó không được lưu lại. Để đặt các biến trong shell hiện tại, thay vào đó hãy lấy nguồn tệp.
Tiếp tục học hỏi
Cần trợ giúp về kiểm tra, WordPress hoặc SEO?
Chúng tôi giúp các nhóm phát triển phần mềm tốt hơn và phát triển trang web của họ. Hãy cho chúng tôi biết bạn đang làm gì và chúng tôi sẽ đề xuất bước tiếp theo rõ ràng.
Thêm hướng dẫn về thử nghiệm, AI và phát triển hiện đại
Bạn thích bài viết này? Chọn một chủ đề và chuyển ngay sang phần hướng dẫn mới nhất của chúng tôi.
Giải thích về ảo giác AI: Tại sao các mô hình ngôn ngữ lại tạo nên mọi thứ và cách giảm thiểu nó
Ảo giác AI đã giải thích: chúng là gì, tại sao mô hình ngôn ngữ tạo ra chúng, các loại phổ biến, cách giảm thiểu chúng bằng cách căn cứ và nhắc nhở …
Đọc bài viết →DevOpsGiải thích về các biến môi trường: Tệp .env, bí mật và cấu hình được thực hiện đúng
Các biến môi trường được giải thích bằng các ví dụ Python mà chúng tôi đã chạy: đọc và thiết lập chúng, tại sao mọi giá trị đều là một chuỗi, tệp .e…
Đọc bài viết →Phát triển phần mềmGiải thích về giới hạn tỷ lệ API: Thuật toán, tiêu đề và xử lý lỗi 429
Giới hạn tốc độ API được giải thích bằng mô phỏng mà chúng tôi đã chạy: nhóm mã thông báo, cửa sổ cố định, cửa sổ trượt, 429 phản hồi, tiêu đề giới …
Đọc bài viết →Tự động hóa kiểm thửChế giễu bằng Python: unittest.mock, patch và những sai lầm khiến bài kiểm tra nói dối
Mô phỏng bằng Python với unittest.mock, đã được thử nghiệm: Mock và return_value, side_effect, vá lỗi nơi sử dụng tên, thông số kỹ thuật và thông số…
Đọc bài viết →Kiểm thử phần mềmGiải thích về phạm vi bảo hiểm của mã: Phạm vi tuyến so với chi nhánh và ý nghĩa thực sự 100%
Phạm vi bảo hiểm mã được giải thích bằng cách chạy phạm vi bảo hiểm thực tế: phạm vi bảo hiểm dòng, chi nhánh và điều kiện, đọc báo cáo, lý do tại s…
Đọc bài viết →Cơ sở dữ liệu & SQLDELETE vs TRUNCATE vs DROP trong SQL: Sự khác biệt, khôi phục và thời điểm sử dụng mỗi loại
DELETE vs TRUNCATE vs DROP được giải thích bằng SQL đã được thử nghiệm: mỗi loại loại bỏ, khôi phục, kích hoạt, đặt lại danh tính, tốc độ và Postgre…
Đọc bài viết →Cơ sở dữ liệu & SQLGiải thích về Chế độ xem SQL: Tạo, sử dụng và tránh những cạm bẫy, kèm theo ví dụ
Chế độ xem SQL được giải thích bằng các truy vấn chúng tôi đã chạy: tạo chế độ xem, kết quả trực tiếp, ẩn cột, hiệu suất, chế độ xem có thể cập nhật…
Đọc bài viết →Phát triển phần mềmGiao diện TypeScript so với loại: Sự khác biệt thực sự, được thử nghiệm bằng trình biên dịch
Giao diện TypeScript so với loại, được kiểm tra bằng trình biên dịch thực: cả hai có thể làm gì, chỉ một người có thể làm gì, hợp nhất khai báo, mở …
Đọc bài viết →Phát triển phần mềmGiải thích về Python *args và **kwargs: Hàm linh hoạt với ví dụ rõ ràng
Python *args và **kwargs được giải thích bằng mã chúng tôi đã chạy: đóng gói và giải nén, thứ tự tham số, đối số chỉ từ khóa và chỉ vị trí, trình tr…
Đọc bài viết →Phát triển phần mềmHàm Lambda của Python: Cú pháp, cách sử dụng thực tế và khi nào không nên sử dụng chúng
Các hàm lambda của Python được giải thích bằng mã chúng tôi đã chạy: cú pháp, sắp xếp bằng khóa, bản đồ và bộ lọc, các phần đóng, bẫy liên kết muộn …
Đọc bài viết →Trí tuệ nhân tạoĐại lý AI là gì? Cách chúng hoạt động, với vòng lặp tác nhân đơn giản, bạn có thể chạy
Tác nhân AI là gì? Hướng dẫn đơn giản về vòng lặp tác nhân, công cụ, bộ nhớ và lan can, với một vòng lặp Python nhỏ mà chúng tôi đã chạy, những rủi …
Đọc bài viết →DevOpsGiám sát và Khả năng quan sát: Sự khác biệt thực sự, với nhật ký, số liệu và dấu vết
Giải thích về giám sát và khả năng quan sát: lỗi đã biết và chưa biết, nhật ký, số liệu và dấu vết, hướng dẫn về sự cố thanh toán, tín hiệu vàng, Op…
Đọc bài viết →DevOpsDocker Volumes vs Bind Mounts: Duy trì dữ liệu vùng chứa đúng cách
Giải thích về khối lượng Docker và liên kết gắn kết: tại sao dữ liệu vùng chứa biến mất, khối lượng được đặt tên, liên kết gắn kết, tmpfs, ví dụ về …
Đọc bài viết →Tự động hóa kiểm thửTDD vs BDD: Key Differences, One Feature Built Both Ways and When to Use Each
TDD và BDD được giải thích bằng một tính năng giảm giá được xây dựng theo cả hai cách: tái cấu trúc màu đỏ-xanh với mã chúng tôi đã chạy, kịch bản C…
Đọc bài viết →Phát triển phần mềmGit merge vs rebase: khác biệt, ví dụ và khi nào dùng cái nào
Git merge vs rebase được giải thích bằng đầu ra log thực tế, bảng so sánh, quy tắc vàng của rebase và quy trình làm việc nhóm an toàn.
Đọc bài viết →Phát triển phần mềmList comprehension trong Python: cú pháp, ví dụ và khi nào nên tránh
List comprehension trong Python: cú pháp, lọc dữ liệu, if-else, vòng lặp lồng nhau, thời gian đo thực tế và khi nào vòng lặp for thông thường tốt hơn.
Đọc bài viết →Cơ sở dữ liệu & SQLGiải thích SQL JOIN kèm ví dụ (Inner, Left, Right, Full)
Hiểu SQL JOIN từng bước với bảng và truy vấn mẫu: INNER, LEFT, RIGHT, FULL, CROSS và SELF, cùng lỗi thường gặp và mẹo hiệu năng.
Đọc bài viết →