Deploy và biến môi trường — Từ chạy được ở máy bạn đến khi công khai

Bài viết này nằm trong khóa Kiến thức nền tảng về CNTT, xây dựng từ đầu những kiến thức CNTT thực tế tối thiểu mà bạn cần để lập trình và vibe coding.
Khi ở máy bạn thì chạy được nhưng công khai xong lại không chạy, nguyên nhân nằm ngoài mã nguồn. Các sơ đồ sẽ cho bạn thấy ba giai đoạn build, triển khai, công khai, và những biến môi trường đổi giá trị theo từng môi trường.

Bài viết này bàn về deploy, tức các bước đưa một ứng dụng đang chạy ở máy bạn ra công khai, và những biến môi trường đổi giá trị theo từng nơi chạy.

Cùng một app.js chạy ở hai nơi: máy tính của bạn và server công khai.

Khi ở máy bạn thì chạy được mà trên server công khai lại không, nguyên nhân nằm ngoài mã nguồn.

Một app.js chạy ở hai nơi
app.jschỉ viết một bảnChạy thẳngnode app.jsDeploybuild → công khaiMáy tính của bạnlocalhost:3000Server công khaimy-app.example.comChỉ mình bạnBất kỳ ai
app.js bạn viết chỉ có một. Đường trên là chạy thẳng ngay tại máy bạn, đường dưới là deploy rồi chạy trên server công khai. Đầu bên phải là những ai mở được URL đó.

Khác biệt giữa hai nơi này chính là lý do công khai xong thì không chạy.

Deploy gồm ba giai đoạn: build, triển khai và công khai

Deploy là công việc đưa ứng dụng lên server công khai để bất kỳ ai cũng dùng được, và bên trong nó là ba giai đoạn: build, triển khai và công khai.

Ba giai đoạn của deploy, và điều bạn thấy khi một giai đoạn hỏng
BuildTriển khaiCông khaiGom app.js vàcác file cần thiếtGửi lên serverrồi khởi độngTiếp nhận requestđến URLThư mục bản buildmy-app đã chạymy-app.example.commở đượcKhông triển khaiDừng khi khởi độngGiữ bản build cũ
Đọc từ trên xuống. Cột trái là giai đoạn, hai cột giữa là việc diễn ra ở đó và thứ nó tạo ra, cột phải là điều bạn thấy khi giai đoạn đó hỏng.

Giai đoạn thứ nhất là build (build: gom app.js và các file cần thiết về một dạng mà server công khai chạy được), và bộ file tạo ra là bản build (build output).

Thứ build gom lại thành một, và thứ không vào bản build
app.jsbạn tự viếtThư viện bên ngoàimã người khác viếtFile cấu hình chỉdùng ở máy bạnBuildThư mục bản buildgửi lên serverỞ lại máy bạnkhông được gửi
app.js và các thư viện bên ngoài gộp lại thành một bản build rồi được gửi lên server công khai. File cấu hình chỉ dùng ở máy bạn thì không được gom, và ở lại máy bạn.

File cấu hình chỉ dùng ở máy bạn thì không vào bản build và ở lại máy bạn.

Thứ tới được server công khai chỉ là bản build đã gom xong.

Quay về bản build trước đó là rollback.

Từ deploy đôi khi chỉ nói tới giai đoạn triển khai, đôi khi nói tới cả ba giai đoạn.

Bạn cũng sẽ gặp cách nói CI/CD (Continuous Integration / Continuous Delivery: cơ chế tự động chạy ba giai đoạn này mỗi lần bạn ghi lại mã nguồn), nhưng chỉ phần tự động là mới, còn các giai đoạn thì vẫn thế.

Deploy là tên gọi chung cho ba việc

Deploy là làm liên tiếp ba việc: gom lại, gửi đi, và tiếp nhận.

Gom lại là build, gửi đi rồi khởi động là triển khai, tiếp nhận request đến URL là công khai, còn file cấu hình chỉ dùng ở máy bạn thì không nằm trong thứ được gom nên không tới được server công khai.

Môi trường phát triển và môi trường production — cùng một mã nguồn, nhưng thứ bao quanh thì khác

Khi app.js chạy được ở máy bạn mà không chạy trên server công khai, đó không phải vì mã nguồn đã đổi.

Đó là vì môi trường (environment: bộ gồm máy tính, hệ điều hành, môi trường chạy, cơ sở dữ liệu và các giá trị cấu hình được chuẩn bị để chạy app.js) thì khác nhau.

Trong bài "Môi trường phát triển là gì", môi trường phát triển là tổ hợp các công cụ; còn ở đây nó là nơi bạn chạy mọi thứ bằng những công cụ đó.

Bên trong hai môi trường cùng chạy một app.js
Hai môi trường, một app.js
  • app.js bạn chạy là cùng một file ở cả hai môi trường
Phát triển — máy tính của bạn
  • node do bạn tự cài
  • Cơ sở dữ liệu nằm trong cùng máy tính đó
  • Cấu hình in chi tiết lỗi ra màn hình
Production — server công khai
  • node đã cài sẵn trên server
  • Cơ sở dữ liệu nằm trên một server khác
  • Cấu hình không in lỗi ra màn hình mà ghi vào log
Khung ngoài là toàn bộ thứ chạy app.js, còn hai khung bên trong là hai môi trường. app.js bạn chạy chỉ có một, nhưng thứ được chuẩn bị xung quanh nó thì khác nhau.

Bên trong khung ngoài có hai môi trường, và app.js chúng chạy là cùng một file.

Chỉ mã nguồn là giống nhau; URL bạn mở, nơi đặt cơ sở dữ liệu và API key đều khác.

Giả định đúng ở máy bạnTrên server công khaiĐiều xảy ra khi công khai
Kết nối tới localhost:5432Không có cơ sở dữ liệu tên đóLỗi khi thao tác lưu
API key là key thử nghiệmKey thử nghiệm không được chấp nhậnDịch vụ bên ngoài từ chối
In lỗi ra màn hìnhHiện nguyên vẹn trên màn hình người dùngNgười ta nhìn thấy chi tiết
Nội dung của app.jsVẫn là app.js đóChạy y như vậy

Ba dòng trên là những giả định chỉ đúng ở máy bạn.

Khi công khai xong mà không chạy, hãy nghi phần bên ngoài trước khi nghi mã nguồn.

Nếu bạn vào server công khai và sửa tay bản build hay cấu hình, lần deploy sau sẽ ghi đè và những sửa đổi đó biến mất.

Hãy sửa app.js trong môi trường phát triển, và sửa xong thì deploy thêm một lần nữa.

Chỉ mã nguồn là giống nhau

Môi trường là toàn bộ những thứ được chuẩn bị xung quanh app.js để nó chạy được.

Máy tính của bạn là môi trường phát triển, server công khai là môi trường production, và ngay cả khi hai bên có những thứ cùng vai trò thì nơi kết nối lẫn key vẫn mang giá trị khác nhau, nên khi công khai xong mà không chạy, hãy bắt đầu kiểm tra từ phần bên ngoài này.

Biến môi trường đổi giá trị theo từng môi trường mà không phải sửa mã nguồn

Việc đặt giá trị ra ngoài mã nguồn và để mã nguồn chỉ đọc tên chính là biến môi trường (environment variable: giá trị được đặt bên ngoài chương trình và được đọc theo tên lúc chạy).

Thứ bạn viết trong app.js chỉ là tên, còn ở máy bạn thì bạn viết giá trị vào .env (file liệt kê tên và giá trị của các biến môi trường).

Tên và giá trị của DATABASE_URL được đặt ở đâu
Máy tính của bạn (môi trường phát triển)
Thư mục my-app
  • app.js — chỉ viết tên DATABASE_URL
  • .env — viết giá trị, DATABASE_URL=localhost:5432
  • .env không vào bản build và cũng không ghi vào Git
Server công khai (môi trường production)
Thư mục bản build
  • app.js — tới nơi mà bên trong vẫn chỉ có cái tên
Cấu hình nơi deploy
  • Đăng ký DATABASE_URL=db.example.com
  • app.js đang chạy đọc giá trị này theo tên
Bên trái là máy tính của bạn, bên phải là server công khai. Thứ bạn viết trong app.js chỉ là tên; giá trị nằm ở .env bên trái, và ở phần cấu hình của nơi deploy bên phải.

.env ở máy bạn chỉ được đọc bên trong chính máy tính bạn đặt nó.

Vì .env chứa API key nên nó không được ghi vào Git, và cũng không nằm trong bản build nên không tới được môi trường production.

Khi tài liệu hướng dẫn nói "hãy thiết lập biến môi trường cho production", đó là đăng ký đúng những cái tên ấy ở phía server công khai.

Nơi khởi độngGiá trị lấy từ đâuGiá trị của DATABASE_URLKết quả kết nối
Máy tính của bạn.env ở máy bạnlocalhost:5432Tới cơ sở dữ liệu ở máy bạn
Server công khai, đã đăng kýCấu hình nơi deploydb.example.comTới cơ sở dữ liệu production
Server công khai, quên đăng kýKhông có ở đâu cảVẫn rỗngKhông kết nối được, dừng với lỗi

Vẫn là app.js đó, nhưng giá trị nó đọc thay đổi theo nơi khởi động.

Giá trị được đọc vào lúc khởi động, nên đổi giá trị xong thì bạn khởi động lại.

Thứ đang chạy trong khung trái và khung phải là cùng một app.js.

Chỉ khác nhau ở những giá trị đặt bên ngoài nó.

Tên nằm trong mã nguồn, giá trị nằm ở môi trường

Biến môi trường là giá trị được đặt bên ngoài mã nguồn và được gọi ra theo tên.

Ở máy bạn thì bạn viết nó vào file tên là .env, còn trên server công khai thì bạn đăng ký đúng cái tên đó như một cấu hình của server ấy, nên khi công khai xong mà không chạy, hãy xem trước tiên là bạn có quên bước đăng ký này không.

Thông tin bí mật chỉ đặt trong biến môi trường, không đưa vào mã nguồn và bản build

Trong các giá trị bạn đặt vào biến môi trường, thứ cần để ý nhất là thông tin bí mật (secret: chuỗi ký tự mà người khác biết được thì mạo danh bạn được), tiêu biểu là API key và mật khẩu.

Với biến môi trường mà frontend đọc, giá trị được ghi thẳng vào bản build ngay lúc build.

Giá trị đã ghi vào đó sẽ tới nguyên vẹn thiết bị của bất kỳ ai mở URL.

Thông tin bí mật chỉ đặt trong biến môi trường production, thứ không vào bản build, và bạn đăng ký chúng ở màn hình cấu hình của nơi deploy.

Giá trị đã đăng ký không nằm trong bản build; server công khai truyền nó vào lúc khởi động app.js.

Nếu bạn quên đăng ký, nó khởi động với cái tên có sẵn nhưng không có giá trị.

Giá trị đã đăng ký chỉ tồn tại ở phía server công khai, và giá trị đó không được chép sang bản build hay .env ở máy bạn.

Sau khi công khai, bản thân giá trị chỉ tồn tại ở đúng một nơi.

Sau khi công khai, giá trị của API key nằm ở đâu
Server công khai
Biến môi trường prod
  • API_KEY chứa giá trị dùng cho production
  • Chỉ app.js đang chạy và người thao tác được trên server mới đọc được
Thư mục bản build
  • Trong app.js chỉ có cái tên API_KEY
  • Bản thân giá trị thì không được viết ở đó
Thiết bị người dùng
  • Thứ tới nơi chỉ là HTML và JavaScript của màn hình
  • Không chứa giá trị của thông tin bí mật
Bên trái là bên trong server công khai, bên phải là thiết bị của người dùng. Giá trị của API key chỉ nằm trong biến môi trường production.

Giá trị chỉ nằm trong biến môi trường production, không nằm trong bản build và cũng không nằm trên thiết bị của người dùng.

Bài "Những điểm cần chú ý về bảo mật" sẽ bàn về cách nhìn xem mỗi chỗ đặt giá trị có bao nhiêu người đọc được.

Gom chỗ đặt giá trị về đúng một nơi

Thông tin bí mật là chuỗi ký tự mà nếu bị lộ thì người khác sẽ dùng được nó.

Viết nó vào mã nguồn thì build sẽ gom nó lại và đưa tới tận thiết bị của người dùng, nên thay vì viết giá trị, bạn hãy đăng ký nó như một cấu hình trên server công khai và chỉ viết cái tên trong mã nguồn.

QUIZ

Kiểm tra kiến thức

Hãy trả lời từng câu hỏi một.

Câu 1Trong ba giai đoạn của deploy, giai đoạn nào gom app.js và các file cần thiết về một dạng mà server công khai chạy được?

Câu 2my-app chạy được ở máy bạn, nhưng công khai xong thì dừng với lỗi. Nguyên nhân nào nêu trong bài là đúng?

Câu 3Nơi đặt thông tin bí mật như API key hay mật khẩu cơ sở dữ liệu, chỗ nào đúng với phần bài đã trình bày?