Docker dùng để làm gì? Hướng dẫn Docker cho người mới bắt đầu

Docker dùng để làm gì? Hướng dẫn Docker cho người mới bắt đầu

Là một nhà phát triển, bạn có thể đã nghe nói về Docker tại một số thời điểm trong cuộc đời làm nghề của mình. Và bạn có thể biết rằng nó đã trở thành công nghệ quan trọng mà bất kỳ nhà phát triển ứng dụng nào cũng phải biết.

Nếu bạn không biết tôi đang nói về điều gì, đừng lo lắng – đó là những gì bài viết này dành cho bạn.

Chúng ta sẽ bắt đầu một cuộc hành trình để khám phá Docker, mọi người đang nói về cái gì và bạn có thể làm gì với nó. Cuối cùng, chúng ta cũng sẽ tạo, xuất bản và chạy hình ảnh Docker đầu tiên của mình.

Lược sử về container

Docker là một trình thực thi container. Nhiều người nghĩ rằng Docker là loại đầu tiên của loại hình này, nhưng điều này không đúng – các Linux Container đã tồn tại từ những năm 1970.

Docker quan trọng đối với cả cộng đồng phát triển và cộng đồng container bởi vì nó làm cho việc sử dụng container trở nên dễ dàng đến mức mọi người đều bắt đầu thực hiện.

Container là gì?

Container hay Linux Container là một công nghệ cho phép chúng ta cô lập một số quy trình nhân nhất định và đánh lừa chúng nghĩ rằng chúng là những tiến trình duy nhất đang chạy trong một máy tính hoàn toàn mới.

Khác với Máy ảo (Virtual Machine – VM), một Container có thể chia sẻ hạt nhân của hệ điều hành trong khi chỉ tải các mã nhị phân / thư viện khác nhau của chúng.

Nói cách khác, bạn không cần phải cài đặt toàn bộ hệ điều hành khác nhau (được gọi là hệ điều hành khách – Guest Operator System) bên trong hệ điều hành máy chủ của mình. Bạn có thể có một số Container chạy trong một hệ điều hành duy nhất mà không cần cài đặt một số hệ điều hành khách khác nhau.

Sự khác biệt giữa Máy ảo và Docker Container

Điều này làm cho các container nhỏ hơn, nhanh hơn và hiệu quả hơn nhiều. Trong khi một máy ảo có thể mất khoảng 1 phút để khởi chạy có thể nặng vài Gigabyte, trung bình một container nặng từ 400 MB đến 600 MB (container lớn nhất).

Chúng cũng chỉ mất vài giây để khởi chạy. Điều này chủ yếu là do chúng không phải khởi chạy toàn bộ hệ điều hành trước khi chạy tiến trình.

Và tất cả điều này bắt đầu với sáu ký tự.

Sự khởi đầu của container

Lịch sử của container bắt đầu từ năm 1979 với Unix v7. Khi đó, tôi thậm chí còn chưa được sinh ra, và bố tôi mới 15 tuổi. Các container đã tồn tại vào năm 1979? Không!

Năm 1979, Unix phiên bản 7 đã giới thiệu một lệnh gọi hệ thống tên là chroot, đây là sự khởi đầu của những gì chúng ta biết ngày nay là tiến trìnhảo hóa.

Lệnh chroot cho phép hạt nhân thay đổi thư mục gốc rõ ràng của một tiến trình và các con của nó.

Nói tóm lại, tiến trình cho rằng nó đang chạy một mình trong máy, bởi vì hệ thống tệp của nó được tách biệt khỏi tất cả các tiến trình khác. Điều này tương tự với syscall được giới thiệu trong BSD vào năm 1982. Nhưng phải hai thập kỷ sau, chúng ta mới có ứng dụng rộng rãi đầu tiên của nó.

Vào năm 2000, một nhà cung cấp dịch vụ lưu trữ đang tìm kiếm những cách tốt hơn để quản lý trang web của khách hàng của họ, vì tất cả chúng đều được cài đặt trong cùng một máy và cạnh tranh cho cùng một tài nguyên.

Giải pháp này được gọi là jails, và đây là một trong những nỗ lực thực sự đầu tiên để cô lập mọi thứ ở cấp độ tiến trình. Jails cho phép bất kỳ người dùng FreeBSD nào phân vùng hệ thống thành một số hệ thống độc lập, nhỏ hơn (được gọi là jails). Mỗi jails có thể có cấu hình IP và cấu hình hệ thống riêng.

Jails là giải pháp đầu tiên để mở rộng việc sử dụng chroot để cho phép không chỉ phân tách ở cấp hệ thống tệp, mà còn ảo hóa người dùng, mạng, hệ thống con, v.v.

Năm 2008, LXC (LinuXContainers) được ra mắt. Vào thời điểm đó, đây là lần triển khai đầu tiên và hoàn chỉnh nhất của hệ thống quản lý container. Nó sử dụng các nhóm điều khiển, không gian tên và rất nhiều thứ đã được xây dựng cho đến lúc đó. Tiến bộ lớn nhất là nó được sử dụng trực tiếp từ nhân Unix, nó không yêu cầu bất kỳ bản vá lỗi nào.

Docker

Cuối cùng, vào năm 2010, Solomon Hykes và Sebastien Pahl đã tạo ra Docker trong nhóm ươm tạo khởi nghiệp Y Combinator. Năm 2011, nền tảng này đã được ra mắt.

Ban đầu, Hykes bắt đầu dự án Docker ở Pháp như một phần của dự án nội bộ trong dotCloud, một công ty PaaS đã ngừng hoạt động vào năm 2016.

Docker không bổ sung nhiều vào trình thực thi container vào thời điểm đó – đóng góp lớn nhất từ ​​Docker cho hệ sinh thái container là nhận thức. CLI và các khái niệm dễ sử dụng của nó đã dân chủ hóa việc sử dụng container cho các nhà phát triển thông thường và không chỉ cho các công ty cần container vì một số lý do.

Sau năm 2013, một số công ty bắt đầu áp dụng Docker làm trình thực thi container mặc định vì nó đã tiêu chuẩn hóa việc sử dụng các container trên toàn thế giới. Năm 2013, Red Hat công bố sự hợp tác của Docker, năm 2014 là thời điểm của Microsoft, AWS, Stratoscale và IBM.

Vào năm 2016, phiên bản đầu tiên của Docker cho một hệ điều hành khác với Linux đã được công bố. Windocks đã phát hành một cổng của dự án OSS của Docker được thiết kế để chạy trên Windows. Và vào cuối năm đó, Microsoft thông báo rằng Docker hiện đã được hỗ trợ trên Windows thông qua Hyper-V.

Vào năm 2019, Microsoft đã công bố WSL2, giúp Docker có thể chạy trên Windows mà không cần máy ảo hóa trên Hyper-V. Docker bây giờ đã đa nền tảng trong khi vẫn tận dụng cách tiếp cận vùng chứa của Linux.

Cuối cùng, vào năm 2020, Docker đã trở thành sự lựa chọn trên toàn thế giới cho các container. Điều này xảy ra không nhất thiết vì nó tốt hơn những cái khác, mà vì nó hợp nhất tất cả các triển khai trong một nền tảng dễ sử dụng duy nhất với CLI và Daemon. Và nó thực hiện tất cả những điều này trong khi sử dụng các khái niệm đơn giản mà chúng ta sẽ khám phá trong các phần tiếp theo.

Docker hoạt động như thế nào?

Docker đóng gói một ứng dụng và tất cả các phụ thuộc của nó trong một container ảo có thể chạy trên bất kỳ máy chủ Linux nào. Đây là lý do tại sao chúng tôi gọi chúng là container. Bởi vì chúng có tất cả các phụ thuộc cần thiết có trong một phần mềm duy nhất.

Docker bao gồm các phần tử sau:

  • Daemon – được sử dụng để xây dựng, chạy và quản lý các container.
  • API cấp cao – cho phép người dùng giao tiếp với Daemon.
  • CLI – giao diện mà chúng tôi sử dụng để cung cấp tất cả những thứ này.

Docker Container

Container là phần trừu tượng của lớp ứng dụng. Chúng đóng gói tất cả mã, thư viện và các phần phụ thuộc lại với nhau. Điều này giúp nhiều container có thể chạy trên cùng một máy chủ lưu trữ, vì vậy bạn có thể sử dụng tài nguyên của máy chủ đó hiệu quả hơn.

Mỗi container chạy như một tiến trình độc lập trong không gian người dùng và chiếm ít dung lượng hơn các máy ảo thông thường do kiến ​​trúc phân lớp của chúng.

Các lớp này được gọi là intermediate images (hình ảnh trung gian) và những image này được tạo mỗi khi bạn chạy một lệnh Dockerfile mới, ví dụ: nếu bạn có tệp Dockerfile như sau:

FROM node:stable
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN npm install grpc
RUN npm install
ENTRYPOINT ["npm", "start"]

Tại mỗi lệnh như COPY hoặc RUN bạn sẽ tạo một layer khác phía trên container image của mình. Điều này cho phép Docker phân chia và tách từng lệnh thành một phần riêng biệt. Vì vậy, nếu cuối cùng bạn sử dụng image node:stable này lại, nó sẽ không cần phải tải xuống tất cả các layer của nó, bởi vì bạn đã cài đặt image này.

Ngoài ra, tất cả các layer đều được băm, có nghĩa là Docker có thể lưu vào bộ nhớ cache các layer đó và tối ưu hóa thời gian xây dựng cho các layer không thay đổi trên các bản build. Bạn sẽ không cần phải xây dựng lại và sao chép lại tất cả các tệp nếu bước COPY không thay đổi, điều này giúp giảm đáng kể thời gian dành cho quy trình xây dựng.

Trong phần cuối của quá trình xây dựng, Docker tạo một layer trống mới phía trên tất cả các layer được gọi là thin writable layer (layer mỏng có thể ghi).Layer này là layer bạn truy cập khi sử dụng docker exec -it <container> <command>. Bằng cách này, bạn có thể thực hiện các thay đổi tương tác trong image và gửi những thay đổi đó bằng cách sử dụng docker commit, giống như cách bạn làm với tệp được theo dõi Git.

Các layer có thể được băm khác nhau giữa các phiên bản. Bằng cách này, Docker có thể kiểm tra xem một layer có thay đổi khi xây dựng image hay không và quyết định xem có nên xây dựng lại nó hay không, tiết kiệm rất nhiều thời gian.

Vì vậy, nếu bạn đã tải xuống image Ubuntu trên máy tính của mình và bạn đang xây dựng một image mới dựa trên một hoặc nhiều layer của image đó, Docker sẽ không tạo lại chúng. Nó sẽ chỉ sử dụng lại các layer tương tự.

Docker layer

Tại sao Docker Container lại tuyệt vời

Bạn có thể đã nghe câu nói mang tính biểu tượng “Nó chạy tốt trên máy của tôi”. Vậy tại sao chúng ta không đưa chiếc máy đó cho khách hàng?

Đó chính xác là vấn đề Docker và các container nói chung giải quyết. Docker Container là một bộ sưu tập đóng gói của tất cả các thư viện và thành phần phụ thuộc của ứng dụng đã được tạo sẵn và sẵn sàng được thực thi.

Rất nhiều công ty đã chuyển từ máy ảo sang container không chỉ vì chúng nhẹ hơn và khởi chạy nhanh hơn nhiều, mà còn vì chúng cực kỳ dễ bảo trì.

Một container duy nhất có thể được tạo phiên bản bằng cách sử dụng Dockerfile của nó (chúng ta sẽ đi đến image trong phần tiếp theo), do đó, điều này giúp một nhà phát triển (hoặc thậm chí một nhóm nhỏ các nhà phát triển) dễ dàng chạy và duy trì toàn bộ hệ sinh thái của các  nó. Mặt khác, bạn sẽ cần một nhân viên cơ sở hạ tầng chỉ để có thể chạy và quản lý các máy ảo.

Trung tâm dữ liệu của bạn với Máy ảo và Docker Container

Điều này có nghĩa là chúng ta không cần VM nữa? Không, ngược lại, máy ảo vẫn rất cần thiết nếu bạn muốn có toàn bộ hệ điều hành cho từng khách hàng hoặc chỉ cần toàn bộ môi trường như một sandbox. Máy ảo thường được sử dụng làm lớp giữa khi bạn có một máy chủ lớn và một số khách hàng sẽ sử dụng nó.

Tính dễ sử dụng và khả năng bảo trì dẫn chúng ta đến một khía cạnh quan trọng khác về lý do tại sao các container lại tuyệt vời như vậy: một công ty sử dụng container sẽ rẻ hơn so với các máy ảo chính thức.

Điều này không phải vì cơ sở hạ tầng hoặc phần cứng rẻ hơn, mà vì bạn cần ít người hơn để quản lý các container, có nghĩa là bạn có thể tổ chức nhóm của mình tốt hơn để tập trung vào sản phẩm thay vì tập trung vào công việc dọn phòng.

Vẫn liên quan đến tiết kiệm, một máy ảo cỡ vừa có thể chạy khoảng 3 đến 8 container. Nó phụ thuộc vào số lượng tài nguyên mà container của bạn sử dụng và bao nhiêu hệ điều hành cơ bản mà nó cần để khởi động trước khi chạy toàn bộ ứng dụng.

Một số ngôn ngữ, như Go, cho phép bạn xây dựng một image chỉ với chỉ với mã nhị phân đã được biên dịch và không có gì khác. Điều này có nghĩa là Docker Container sẽ tải ít hơn nhiều và do đó sẽ sử dụng ít tài nguyên hơn. Bằng cách này, bạn có thể tạo ra nhiều container hơn cho mỗi máy ảo và sử dụng phần cứng của bạn hiệu quả hơn.

Vì các container được tạo ra là tạm thời, điều này có nghĩa là tất cả dữ liệu bên trong chúng sẽ bị mất khi container bị xóa. Điều này thật tuyệt, vì chúng ta có thể sử dụng container cho các tác vụ có thể bùng nổ như CI.

Việc sử dụng các container đã mang lại một cấp độ cải tiến hoàn toàn mới cho DevOps. Giờ đây, bạn có thể chỉ cần chạy nhiều container, mỗi container thực hiện một bước nhỏ trong quy trình triển khai của bạn và sau đó chỉ cần xóa chúng mà không cần lo lắng nếu bạn đã bỏ lại thứ gì đó.

Bản chất không trạng thái của các container khiến chúng trở thành công cụ hoàn hảo cho khối lượng công việc nhanh chóng.

Bây giờ chúng ta đã thấy các container tuyệt vời như thế nào. Hãy tìm hiểu cách chúng ta có thể xây dựng một container!

Docker Image là gì?

Docker Image là hướng dẫn được viết trong một tệp đặc biệt tên là Dockerfile. Nó có cú pháp riêng và xác định các bước Docker sẽ thực hiện để xây dựng container của bạn.

Vì container chỉ là các layer dựa trên các layer thay đổi, nên mỗi lệnh mới bạn tạo trong Docker Image sẽ tạo một layer mới trong container.

Layer cuối cùng là cái mà chúng ta gọi là thin writable layer (layer mỏng có thể ghi). Một layer trống có thể được thay đổi bởi người dùng và gửi bằng cách sử dụng lệnh docker commit.

Đây là một ví dụ về image đơn giản cho ứng dụng Node.js:

FROM node:stable
COPY . /usr/src/app/
RUN npm install && npm run build
EXPOSE 3000
ENTRYPOINT ["npm", "start"]

Trong ví dụ đơn giản này, chúng ta đang tạo một image mới. Tất cả image đều dựa trên image đã tồn tại hoặc image gốc.

Những image này được tải xuống từ Container Registry, một kho lưu trữ image của các container. Cái phổ biến nhất trong số đó là Docker Hub, nhưng bạn cũng có thể tạo một cái riêng bằng cách sử dụng các giải pháp đám mây như Azure Container Registry.

Khi bạn chạy docker build . trên cùng một thư mục với Dockerfile, Docker Daemon sẽ bắt đầu xây dựng image và đóng gói nó để bạn có thể sử dụng nó. Sau đó, bạn có thể chạy docker run <image-name> để chạy một container mới.

Lưu ý rằng chúng ta đưa ra một số cổng nhất định trong Dockerfile. Docker cho phép chúng ta tách các mạng trong hệ điều hành của riêng mình, có nghĩa là bạn có thể ánh xạ các cổng từ máy tính của mình sang container và ngược lại. Ngoài ra, bạn có thể thực hiện các lệnh bên trong container với docker exec.

Hãy đưa kiến ​​thức này vào thực tế nào!

Cách triển khai ứng dụng của bạn bằng Docker

Đây sẽ là hướng dẫn đơn giản và dễ dàng về cách tạo Docker image cơ bản bằng máy chủ Node.js và làm cho nó chạy trên máy tính của bạn.

Đầu tiên, hãy bắt đầu một dự án mới trong thư mục bạn chọn và chạy lệnh npm init -y để tạo một tệp package.json mới. Bây giờ chúng ta hãy tạo một thư mục khác có tên src. Trong thư mục này, chúng ta sẽ tạo một tệp mới có tên server.js.

Bây giờ, trong tệp package.json của bạn, hãy thay đổi phần tử main thành src/server.js. Ngoài ra, hãy xóa tập lệnh test đã được tạo và thay thế bằng "start": "node src/server.js". Tệp của bạn sẽ như thế này:

{
  "name": "your-project",
  "version": "1.0.0",
  "description": "",
  "main": "src/server.js",
  "scripts": {
    "start": "node src/server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

Bây giờ, hãy tạo một tệp có tên Dockerfile (không có phần mở rộng) để tạo image của chúng ta nào!

FROM node:lts-alpine
COPY . /usr/src/app/
WORKDIR /usr/src/app
EXPOSE 8089
ENTRYPOINT ["npm", "start"]

Giải thích điều này:

  1. Đầu tiên, chúng ta dựa trên image Node từ Docker Hub. Vì image được lưu theo tên của chúng, chúng ta phân biệt các image bằng thẻ của chúng. Bạn có thể kiểm tra tất cả các thẻ ở đây.
  2. Tiếp theo, chúng ta sử dụng lệnh COPY để sao chép tất cả các tệp trong thư mục hiện tại (đang sử dụng .) sang một thư mục mới trong container là /usr/src/app. Thư mục được tạo tự động. Điều này là cần thiết vì chúng ta cần tất cả các tệp ứng dụng của chúng ta trong đó.
  3. Bây giờ chúng ta thay đổi thư mục bắt đầu của mình thành thư mục /usr/src/app, vì vậy chúng ta có thể chạy mọi thứ từ thư mục gốc của ứng dụng của chúng ta.
  4. Chúng ta đưa ra cổng 8089,
  5. Và chúng ta nói rằng, ngay sau khi container của chúng ta chạy, chúng ta sẽ thực thi lệnh “npm start”.

Hãy xây dựng image bằng cách chạy lệnh docker build . -t simple-node-image. Bằng cách này, chúng ta sẽ gắn thẻ cho image của mình và đặt tên cho nó.

Docker build

Bạn sẽ thấy rằng nó sẽ tạo và tải xuống image, cùng với tất cả các layer cần thiết. Hãy chạy image này bằng lệnh sau:

docker run -p 80:8089 simple-node-image 

Chúng ta đang ánh xạ cổng 80 của chúng ta với cổng 8089 bên trong container. Chúng ta có thể kiểm tra điều đó bằng cách nhập lệnh docker ps như sau:

docker ps

Bây giờ hãy thử truy cập localhost:80 và xem điều gì sẽ xảy ra:

Node.js localhost

Docker dùng để làm gì?

Bây giờ chúng ta đã thấy cách xây dựng Docker Container, hãy chuyển sang một số cách sử dụng thực tế của Docker và cách bạn có thể tận dụng tối đa nó.

Cơ sở dữ liệu tạm thời

Bạn đã bao giờ cố gắng phát triển một ứng dụng yêu cầu cơ sở dữ liệu để chạy chưa? Hoặc tệ hơn, đã cố gắng chạy ứng dụng của người khác cần cơ sở dữ liệu mà bạn chưa cài đặt?

Giải pháp cũ là cài đặt cơ sở dữ liệu trước, sau đó chạy ứng dụng. Với Docker, bạn chỉ cần chạy container cơ sở dữ liệu. Hãy chạy một container MongoDB đơn giản như sau:

docker run -p 27017:27017 --name my-ephemeral-db mongo

Đó là nó! Giờ đây, bạn có thể truy cập cơ sở dữ liệu của mình từ máy tính thông qua cổng 27017, giống như cách bạn làm bình thường.

Cơ sở dữ liệu lâu dài

Vấn đề với ví dụ trước là nếu bạn xóa container, tất cả dữ liệu của bạn sẽ bị mất. Vì vậy, điều gì sẽ xảy ra nếu bạn muốn chạy một cơ sở dữ liệu cục bộ mà không cần cài đặt nó, nhưng vẫn giữ dữ liệu sau khi nó bị xóa? Bạn có thể liên kết Docker với một ổ đĩa!

Khi bạn liên kết Docker với một ổ đĩa cục bộ, về cơ bản bạn đang gắn hệ thống tệp của mình vào container hoặc ngược lại. Hãy xem nào:

docker run -p 27017:27017 -v /home/my/path/to/db:/data/db --name my-persistent-db mongo

Trong lệnh này, chúng ta đang gắn thư mục /data/db vào thư mục /home/my/path/to/db. Bây giờ nếu chúng tôi sử dụng docker stop my-persistent-dbdocker rm my-persistent-db tất cả dữ liệu của chúng tôi sẽ tiếp tục được lưu trữ ở đó.

Sau đó, nếu chúng ta cần lại cơ sở dữ liệu, chúng ta có thể gắn kết nó bằng cách sử dụng lệnh tương tự và tất cả dữ liệu sẽ trở lại.

Các công cụ sử dụng một lần

Một điều khác mà tất cả các nhà phát triển đều làm: chúng ta cài đặt các ứng dụng mà chúng ta chỉ sử dụng một lần. Ví dụ: CLI đơn giản để truy cập cơ sở dữ liệu cũ hoặc GUI đơn giản tới một máy chủ CI nào đó.

Nhiều công cụ đã có Docker container và bạn có thể sử dụng chúng như thế này, vì vậy bạn không cần phải cài đặt thêm một công cụ nào khác trong sổ ghi chép của mình.

Ví dụ tốt nhất là Redis. Nó có redis-cli được tích hợp sẵn trong một container khác, vì vậy bạn không cần phải cài đặt redis-cli trong shell của mình nếu bạn không sử dụng nó.

Hãy tưởng tượng bạn tạo ra một phiên bản Redis có docker run -d --name redis redis --bind 127.0.0.1 liên kết với giao diện localhost. Bạn có thể truy cập nó thông qua một container khác bằng cách sử dụng:

docker run --rm -it --network container:redis redis-cli -h 127.0.0.1

Cờ --rm nói Docker rằng nó nên loại bỏ các container ngay khi nó dừng lại, trong khi cờ -it nói với nó chúng ta muốn một phiên tương tác (với một shell) và chúng ta sẽ cần một TTY.

Chạy toàn bộ ngăn xếp

Nếu bạn cần kiểm tra một ứng dụng dựa trên một ứng dụng khác, bạn sẽ làm như thế nào? Docker làm cho nó dễ dàng bằng cách cung cấp docker-compose. Đó là một công cụ khác trong hộp công cụ của bạn cho phép bạn viết mã vào file docker-compose.yml để mô tả môi trường của bạn.

Tệp trông như thế này:

version: "3.8"
services:
  web:
    build: .
    ports:
      - "5000:5000"

  redis:
    image: "redis:alpine"
    ports:
      - "6379:6379"

Như bạn có thể thấy, chúng ta đang định nghĩa hai dịch vụ, một dịch vụ được gọi web và chạy docker build trên web.build đường dẫn. Đó là một Dockerfile.

Sau đó, nó đưa ra cổng 5000 cả trong máy chủ và trong container. Dịch vụ khác là redis sẽ tải xuống và chạy image redis trên cổng 6379.

Phần tốt nhất là lớp mạng được chia sẻ, nói cách khác, bạn có thể truy cập redis từ dịch vụ web.

Bạn có thể chạy tệp này với một cách đơn giản là chạy lệnh docker-compose up và xem điều kỳ diệu đang xảy ra.

Phần kết luận

Tôi hy vọng bạn thích nó và tôi hy vọng điều này sẽ giúp bạn tiếp cận với Docker dễ dàng hơn một chút.

Như bạn có thể thấy, hầu hết các ứng dụng của Docker là giúp các nhà phát triển dễ dàng hơn khi phát triển ứng dụng. Nhưng có nhiều công dụng khác, chẳng hạn như các lớp cơ sở hạ tầng và giúp việc quản lý ứng dụng của bạn dễ dàng hơn rất nhiều.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *