Tích hợp liên tục (Continuous Integration – CI) là một phương pháp phát triển phần mềm trong đó các lập trình viên liên tục tích hợp mã nguồn vào kho lưu trữ chung, đồng thời kiểm tra và xây dựng tự động mỗi khi có thay đổi mã nguồn. Mục tiêu của CI là tăng tốc quá trình phát triển và giảm thiểu nguy cơ phát sinh lỗi nghiêm trọng trong môi trường sản xuất.
Tuy nhiên, việc triển khai CI có thể gặp phải một số thử thách. Các quá trình build tự động được thực hiện trong môi trường khác biệt, nơi các thư viện runtime và cấu hình dịch vụ bên ngoài có thể không giống với môi trường phát triển cục bộ (local) hoặc môi trường phát triển (dev).
Để triển khai CI một cách hiệu quả và ổn định, bạn nên cân nhắc sử dụng máy chủ ảo riêng. Khám phá ngay các gói mua VPS tại DataOnline để bắt đầu xây dựng môi trường CI/CD chuyên nghiệp trên nền tảng Ubuntu chỉ trong vài bước đơn giản.
Docker là một nền tảng container hóa, giúp chuẩn hóa môi trường làm việc, giúp quy trình triển khai ứng dụng trở nên đồng nhất. Đối với nhà phát triển, Docker cho phép bạn mô phỏng môi trường sản xuất trên máy local bằng cách chạy các thành phần ứng dụng trong các container độc lập. Những container này có thể được tự động hóa dễ dàng với Docker Compose, bất kể loại ứng dụng hay hệ điều hành cơ sở.
DataOnline sẽ sử dụng Docker Compose để tự động hóa quy trình CI, thông qua việc:
- Tạo một ứng dụng Python đơn giản dạng “Hello World”,
- Viết một script kiểm thử Bash,
- Xây dựng ứng dụng Python trong hai container:
- Một container chạy ứng dụng,
- Một container Redis dùng làm cơ sở dữ liệu.
- Chuyển toàn bộ môi trường kiểm thử vào docker-compose.test.yml, đảm bảo mỗi lần kiểm thử đều được chạy trong một môi trường đồng nhất và hoàn toàn mới.
Cách tiếp cận này giúp bạn xây dựng môi trường kiểm thử tách biệt nhưng giống hệt môi trường chạy thực tế, bao gồm cả các dependency, mà không phụ thuộc vào ứng dụng hoặc hạ tầng bên dưới.
Yêu Cầu
Trước khi bắt đầu, bạn cần:
- Một máy chủ Ubuntu 16.04 với tài khoản người dùng không phải root và có quyền
sudo
. Xem hướng dẫn Thiết lập máy chủ ban đầu với Ubuntu 16.04. - Docker, cài đặt theo Bước 1 và 2 của hướng dẫn Cách Cài Đặt và Sử Dụng Docker trên Ubuntu 16.04.
- Docker Compose, cài đặt theo Bước 1 của hướng dẫn Cách Cài Đặt Docker Compose trên Ubuntu 16.04.
Bước 1 – Tạo ứng dụng Python “Hello World”
Tạo thư mục mới cho ứng dụng:
cd ~ mkdir hello_world cd hello_world
Tạo và chỉnh sửa file app.py bằng nano:
nano app.py
Thêm nội dung sau:
app.py from flask import Flask from redis import Redis app = Flask(__name__) redis = Redis(host="redis") @app.route("/") def hello(): visits = redis.incr('counter') html = "<h3>Hello World!</h3>" \ "<b>Visits:</b> {visits}" \ "<br/>" return html.format(visits=visits) if __name__ == "__main__": app.run(host="0.0.0.0", port=80)
Khi bạn hoàn tất, hãy lưu và thoát tệp.
app.py
là một ứng dụng web dựa trên Flask kết nối với dịch vụ dữ liệu Redis. Dòng visits = redis.incr('counter')
sẽ tăng số lượt truy cập và lưu trữ giá trị này trong Redis. Cuối cùng, một thông báo “Hello World” kèm theo số lượt truy cập sẽ được trả về dưới dạng HTML.
Ứng dụng của chúng ta có hai thư viện phụ thuộc là Flask và Redis, như bạn có thể thấy ở hai dòng đầu tiên. Những thư viện này cần được khai báo trước khi chúng ta có thể chạy ứng dụng.
Tạo file requirements.txt để khai báo các dependency:
nano requirements.txt
Thêm nội dung:
requirements.txt Flask Redis
Lưu và đóng file. Bây giờ chúng ta đã sẵn sàng Dockerize ứng dụng này.
Bước 2 – Dockerize ứng dụng “Hello World”
Tạo và chỉnh sửa file Dockerfile:
nano Dockerfile
Thêm nội dung:
Dockerfile FROM python:2.7 WORKDIR /app ADD requirements.txt /app/requirements.txt RUN pip install -r requirements.txt ADD app.py /app/app.py EXPOSE 80 CMD ["python", "app.py"]
Giải thích:
FROM python:2.7
: Sử dụng image chính thức của Python 2.7,WORKDIR /app
: Đặt thư mục làm việc trong container,ADD requirements.txt /app/requirements.txt
: Thêm file requirements.txt,RUN pip install -r requirements.txt
: Cài đặt các dependency,ADD app.py /app/app.py
: Thêm mã nguồn ứng dụng,EXPOSE 80
: Mở cổng 80,CMD ["python", "app.py"]
: Chạy ứng dụng khi container khởi động.
Bước 3 – Triển khai ứng dụng
Trong bước này, chúng ta sẽ triển khai ứng dụng, và khi hoàn tất, nó sẽ có thể truy cập được qua Internet. Đối với quy trình triển khai của bạn, bạn có thể coi đây là môi trường dev, staging, hoặc production, vì bạn có thể triển khai ứng dụng theo cùng một cách nhiều lần.
Các tệp docker-compose.yml
và Dockerfile
cho phép bạn tự động hóa việc triển khai môi trường cục bộ bằng cách thực thi:
docker-compose -f ~/hello_world/docker-compose.yml build docker-compose -f ~/hello_world/docker-compose.yml up -d
Dòng đầu tiên sẽ xây dựng image ứng dụng cục bộ từ tệp Dockerfile
. Dòng thứ hai sẽ chạy các container web
và redis
trong chế độ nền (daemon mode
, -d
), như đã được chỉ định trong tệp docker-compose.yml
.
Để kiểm tra xem các container đã được tạo hay chưa, hãy chạy lệnh sau:
docker ps
Lệnh này sẽ hiển thị hai container đang chạy, có tên là helloworld_web_1
và helloworld_redis_1
.
Chúng ta có thể lấy địa chỉ IP của container helloworld_web_1
bằng cách chạy:
WEB_APP_IP=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' helloworld_web_1) echo $WEB_APP_IP
Sau đó, kiểm tra xem ứng dụng web có trả về thông điệp chính xác hay không bằng cách chạy:
curl http://${WEB_APP_IP}:80
Kết quả trả về sẽ giống như sau:
Output <h3>Hello World!</h3><b>Visits:</b> 2<br/>
Số lượt truy cập sẽ tăng lên mỗi lần bạn truy cập vào endpoint này. Bạn cũng có thể truy cập ứng dụng “Hello World” từ trình duyệt bằng cách sử dụng địa chỉ IP công khai của máy chủ Ubuntu của bạn.
Điểm mấu chốt để thiết lập ứng dụng của bạn là:
- Đặt ứng dụng của bạn vào một Docker container riêng biệt.
- Chạy mỗi dịch vụ phụ thuộc trong một container riêng.
- Định nghĩa mối quan hệ giữa các container bằng Docker Compose, như trong ví dụ trên.
Docker Compose được giải thích chi tiết hơn trong bài viết về Docker Compose.
Bước 4 – Tạo Script kiểm thử
Bây giờ, chúng ta sẽ tạo một script kiểm thử cho ứng dụng Python. Đây sẽ là một script đơn giản kiểm tra đầu ra HTTP của ứng dụng.
Script này là một ví dụ về loại kiểm thử mà bạn có thể muốn chạy như một phần của quy trình triển khai tích hợp liên tục (CI/CD).
Tạo file test.sh:
nano test.sh
Thêm nội dung:
test.sh sleep 5 if curl web | grep -q '<b>Visits:</b> '; then echo "Tests passed!" exit 0 else echo "Tests failed!" exit 1 fi
test.sh
kiểm tra khả năng kết nối web cơ bản của ứng dụng “Hello World”.
Nó sử dụng cURL để lấy số lượt truy cập và báo cáo liệu bài kiểm thử có thành công hay không.
Bước 5 – Tạo môi trường kiểm thử
Tạo file docker-compose.test.yml:
nano Dockerfile.test
Dưới đây là nội dung cần thêm vào tệp
Dockerfile.test FROM ubuntu:xenial RUN apt-get update && apt-get install -yq curl && apt-get clean WORKDIR /app ADD test.sh /app/test.sh CMD ["bash", "test.sh"]
Dockerfile.test
mở rộng image chính thức ubuntu:xenial
, cài đặt curl làm thư viện phụ thuộc, thêm test.sh vào hệ thống tệp của image và chỉ định lệnh CMD
để thực thi script kiểm thử bằng Bash.
Sau khi các bài kiểm thử được đóng gói trong Docker (Dockerized), chúng có thể được chạy theo cách lặp lại được và độc lập với môi trường.
Chúng ta cần liên kết container kiểm thử với ứng dụng “Hello World”. Đây chính là lúc Docker Compose phát huy tác dụng.
nano docker-compose.test.yml
Dưới đây là nội dung cần thêm vào tệp
docker-compose.test.yml sut: build: . dockerfile: Dockerfile.test links: - web web: build: . dockerfile: Dockerfile links: - redis redis: image: redis
Phần thứ hai của tệp Docker Compose triển khai ứng dụng web chính và dịch vụ Redis theo cách tương tự như tệp docker-compose.yml
trước đó.
Đây là phần xác định các container web và redis. Tuy nhiên, có một điểm khác biệt:
- Container web không còn mở cổng 80, vì vậy ứng dụng sẽ không thể truy cập từ Internet trong quá trình kiểm thử.
- Điều này đảm bảo rằng chúng ta đang xây dựng ứng dụng và các dịch vụ phụ thuộc giống hệt như khi triển khai thực tế.
Tệp này cũng định nghĩa một container sut
(System Under Test) chịu trách nhiệm chạy các bài kiểm thử tích hợp.
- Container
sut
sử dụng thư mục hiện tại làm thư mục dựng (build context
) và dựa trênDockerfile.test
. - Nó liên kết với container web, giúp
test.sh
có thể truy cập địa chỉ IP của ứng dụng để chạy kiểm thử.
Tùy chỉnh cho ứng dụng của riêng bạn
Lưu ý rằng docker-compose.test.yml
có thể bao gồm hàng chục dịch vụ bên ngoài và nhiều container kiểm thử khác nhau. Docker có thể chạy tất cả các dịch vụ này trên một máy chủ duy nhất, vì mỗi container chia sẻ hệ điều hành nền tảng.
Nếu bạn cần chạy nhiều bài kiểm thử hơn cho ứng dụng của mình, bạn có thể:
- Tạo thêm Dockerfile kiểm thử, tương tự như
Dockerfile.test
. - Thêm nhiều container kiểm thử bên dưới container
sut
trong tệpdocker-compose.test.yml
, tham chiếu đến các Dockerfile kiểm thử bổ sung.
Bước 6 – Kiểm thử ứng dụng “Hello World”
Cuối cùng, bằng cách mở rộng các ý tưởng của Docker từ môi trường cục bộ sang môi trường kiểm thử, chúng ta có thể tự động kiểm thử ứng dụng bằng cách thực thi:
docker-compose -f ~/hello_world/docker-compose.test.yml -p ci build
Lệnh này sẽ dựng (build) các image cục bộ cần thiết cho docker-compose.test.yml
.
Lưu ý:
-f
dùng để chỉ định tệpdocker-compose.test.yml
.-p
dùng để đặt tên dự án cụ thể (ci
).
Chạy lệnh sau để khởi động môi trường kiểm thử mới:
docker-compose -f ~/hello_world/docker-compose.test.yml -p ci up -d
Output Creating ci_redis_1 Creating ci_web_1 Creating ci_sut_1
Thực thi lệnh sau để xem kết quả kiểm thử:
docker logs -f ci_sut_1
Output % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 42 100 42 0 0 3902 0 --:--:-- --:--:-- --:--:-- 4200 Tests passed!
Để xác minh rằng các bài kiểm thử đã thành công, chạy lệnh sau:
docker wait ci_sut_1
Output 0
Sau khi thực thi lệnh này, giá trị của $?
sẽ là 0
nếu các bài kiểm thử thành công. Ngược lại, nếu giá trị khác 0
, điều đó có nghĩa là kiểm thử đã thất bại.
Lưu ý rằng các công cụ CI (Continuous Integration) khác có thể clone kho mã nguồn của bạn và thực thi các lệnh trên để kiểm tra xem các bài kiểm thử có chạy thành công với phiên bản mới nhất của ứng dụng hay không, mà không cần lo lắng về phụ thuộc thời gian chạy hoặc cấu hình dịch vụ bên ngoài.
Vậy là xong! Chúng ta đã chạy thành công các bài kiểm thử trong một môi trường mới được dựng, giống hệt môi trường sản xuất.
Kết luận
Nhờ có Docker và Docker Compose, chúng ta đã có thể tự động hóa:
Xây dựng ứng dụng (Dockerfile
). Triển khai môi trường cục bộ (docker-compose.yml
). Tạo image kiểm thử (Dockerfile.test
). Thực thi kiểm thử tích hợp (docker-compose.test.yml
) cho bất kỳ ứng dụng nào.
Đặc biệt, sử dụng docker-compose.test.yml
để kiểm thử mang lại nhiều lợi ích:
- Tự động hóa → Quá trình thực thi
docker-compose.test.yml
không phụ thuộc vào ứng dụng cần kiểm thử. - Nhẹ (Light-weight) → Có thể triển khai hàng trăm dịch vụ bên ngoài trên một máy chủ duy nhất, giúp mô phỏng các môi trường kiểm thử phức tạp.
- Độc lập với nền tảng (Agnostic) → Không bị ràng buộc bởi nhà cung cấp CI, có thể chạy kiểm thử trên bất kỳ hạ tầng hoặc hệ điều hành nào hỗ trợ Docker.
- Bất biến (Immutable) → Kiểm thử thành công trên máy cục bộ cũng sẽ thành công trên công cụ CI của bạn.
Bài hướng dẫn này đã trình bày một ví dụ về cách kiểm thử một ứng dụng đơn giản “Hello World”.
Bây giờ là lúc bạn áp dụng vào ứng dụng của riêng mình! Hãy Dockerize các tập tin kiểm thử, tạo docker-compose.test.yml
của bạn và kiểm thử ứng dụng trong một môi trường mới, sạch, và bất biến.
Trong quá trình thử nghiệm hoặc chạy CI trên Docker Compose, bạn không nhất thiết phải đầu tư máy chủ vật lý. Hãy tham khảo các lựa chọn thuê VPS giá rẻ để có môi trường triển khai linh hoạt, tiết kiệm chi phí nhưng vẫn đảm bảo hiệu năng và tính bảo mật.