Triển khai stack đến Docker Swarm

Posted: Tháng Mười Hai 8, 2018 in Container, Docker
Thẻ:,

Ở bài trước chúng ta giới thiệu tổng quan về docker swarm mà nói về các tính năng, cũng như chạy một service đơn giản trong docker swarm. Trong phần này chúng ta đi vào phần Deploy một stack (gồm các service/container liên kết với nhau) trong docker swarm mà được gọi là “Docker Stack

Reference: Deploy a stack to a swarm

1. Về Docker Stack

  • Docker Swarm đơn thuần điều phối các container chạy trên nhiều các docker host. Nhưng giữa các container đấy không có sự phụ thuộc lẫn nhau. Chính vì vậy, để liên kết các container ấy thành một ứng dụng chúng ta cần phải thao tác thủ công cho phép kết nối giữa chúng.
  • Docker Compose là công cụ mà cho phép liên kết các container với nhau để chạy ứng dụng. Nhưng các container đấy lại chạy trên cùng một docker host. Vì vậy, nó sẽ có mặt hạn chế khi muốn high availability và horizontal scaling.
  • Với Docker Stack, nó cho phép chúng ta kết nối 2 công nghệ trên với nhau. Nó sử dụng tệp tin docker compose (mặc định docker-compose.yml) để định nghĩa, liên kết các container với nhau và chạy trong một cluster docker host (docker swarm). Hình dưới minh họa về Docker Stack

docker stack - docker swarm

2. Triển khai một stack

Chúng ta thực hiện deploy “LEMP” stack đến docker swarm với Docker Stack.

LEMP là stack gồm: Nginx, MySQL/MariaDB và PHP trên Linux

Thực hiện một số công việc sau:

  • Thiết lập docker swarm
  • Định nghĩa các services cho stack
  • Triển khai docker stack

2.1 Thiết lập docker swarm

Chúng ta tham khảo bài viết trước để thiết lập docker swarm

Xác nhận docker swarm đã tạo xong, với một số node

show docker nodes

2.2 Định nghĩa các services cho stack

Chúng ta thực hiện tạo tệp tin docker-compose.yml để định nghĩa các services cho stack, để cho phép triển khai trên docker swarm.

Tệp docker-compose.yml trong docker swarm không hỗ trợ  tùy chọn “build”, vì vậy mà chúng ta sẽ khai báo các image name trực tiếp ở mỗi service. Để sử dụng image name, chúng ta cần thực hiện build image trước với Dockerfile và push lên registry (private hoặc public).

Step1: Dựng private docker registry

Trong bài viết này tôi sử dụng private registry chứa các image, vì vậy chúng ta cần dựng private docker registry. Tham khảo qua bài viết dựng private docker registry

Nếu sử dụng một số public registry (như hub.docker.com), chúng ta có thể bỏ qua bước này.

Step2: Build các docker image

– Build php7-fpm image

Tạo tệp tin Dockerfile để build image cho php7-fpm với nội dung sau

FROM php:7.2-fpm

RUN apt-get update
RUN apt-get install libxml2-dev unzip libmcrypt-dev zlib1g-dev -y
#Using docker-php-ext-install to install php modules
RUN docker-php-ext-install pdo_mysql mysqli mbstring xml opcache zip

EXPOSE 9000
CMD ["php-fpm", "-F"]

Chúng ta sử dụng image gốc là php:7.2-fpm và thực hiện cài một số thư viện và một số module cho php

Thực hiện build image với tên hub.example.local/php7-fpm

 docker build -t hub.example.local/php7-fpm .

build image php7-fpm

– Build nginx image

Nội dung tệp tệp Dockerfile có nội dung như sau:

FROM nginx:latest

MAINTAINER KeepWalking86

ADD ./default.conf /etc/nginx/conf.d/
RUN mkdir -p /var/www/example/
RUN chown -R nginx:nginx /var/www/example

EXPOSE 80

Trong đó, chúng ta build lại từ image gốc nginx:latest, và thực hiện một số tùy như:

Add tệp ‘default.conf’ để thay nội dung tệp cấu hình chính mặc định của nginx. Nội dung tệp như sau:

server {
        listen 80;
        listen [::]:80 ipv6only=on;

    # Log files for Debugging
        access_log /var/log/nginx/laravel-access.log;
        error_log /var/log/nginx/laravel-error.log;

    # Webroot Directory for Laravel project
        root /var/www/example/public;
        index index.php index.html index.htm;

        # Your Domain Name
        server_name example.local;
        location ~* \.(jpg|jpeg|gif|css|png|js|ico|txt|srt|swf|zip|rar|html|htm|pdf)$ {
                access_log        off;
                log_not_found     off;
                expires 30d; # caching, expire after 30 days
        }

        location / {
                try_files $uri $uri/ /index.php?$query_string;
        }

    # PHP-FPM Configuration Nginx
        location ~ \.php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                #fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
                fastcgi_index index.php;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include fastcgi_params;
                fastcgi_pass web-phpfpm:9000;
        }
}

Ở đây, tôi khai báo một host với tên site “example.local”; vị trí thư mục root của website là “/var/www/example/public”. Website chạy php và sử dụng bộ dịch là FastCGI. Trong đó sử dụng chỉ thị “fastcgi_pass” để thiết lập kết nối đến server FastCGI, mà trong trường  hợp này là kết nối đến server web-phpfpm:9000. Ở đây web-phpfpm là tên service mà ta sẽ định nghĩa ở trong tệp docker-compose.yml trong Step tiếp theo.

Thực hiện build image với tên hub.example.local/nginx

docker build -t hub.example.local/nginx .

build nginx nginx

Step3: Tạo tệp tin docker-compose.yml

Chúng ta định nghĩa 03 service: db, web-phpfpm và web-nginx.

Một số tùy chọn cho mỗi service ở tệp docker compose như sau:

– image: xác định tên image để tạo container

– deploy: tùy chọn cấu hình cho việc triển khai

+ replicas: Số lượng bản sao container

+ constraints: [node.role == worker/manager] → Xác định container sẽ được triển khai ở host nào trong docker swarm. Ở đây, chúng ta xác định vị trí docker host theo vai trò manager/worker trong docker swarm.

– volumes: để mount source code giữa docker host và container. Mục đích của tôi là muốn mount source code tại thư mục ./src trên node manager vào web-phpfpm và web-nginx.

– Ở service “db”, tôi khai báo một số biến ENV để tạo thông tin database như: user, password, … Sau khi tạo và chạy stack, chúng ta có thể kiểm tra kết nối web và db

Tham khảo thêm về cấu hình tệp docker-compose cho docker stack từ:

https://docs.docker.com/compose/compose-file/#deploy

Nội dung tệp docker-compose.yml như sau:

version: "3"

services:
    db:
      image: mariadb:latest
      deploy:
        replicas: 2
        placement:
          constraints: [node.role == worker]
      networks:
        - mynet
      environment:
        MYSQL_ROOT_PASSWORD: P@ssw0rd
        MYSQL_DATABASE: laravel
        MYSQL_USER: keepwalking
        MYSQL_PASSWORD: P@ssw0rd

    web-phpfpm:
      image: hub.example.local/php7-fpm
      deploy:
        replicas: 2
        placement:
          constraints: [node.role == manager]
      volumes:
        - ./src:/var/www/example
      networks:
        - mynet

    web-nginx:
      image: hub.example.local/nginx
      deploy:
        replicas: 2
        placement:
          constraints: [node.role == manager]
      depends_on:
        - db
        - web-phpfpm
      ports:
        - 8080:80
      networks:
        - mynet
      volumes:
        - ./src:/var/www/example

networks:
  mynet:

2.3 Thực hiện deploy docker stack

Chúng ta thực hiện lệnh sau để triển khai stack với tên “lemp”

​docker stack deploy --compose-file docker-compose.yml lemp

Thực hiện một số lệnh sau để kiểm tra stack

Check docker stack lemp

Như vậy, chúng ta đã tạo lemp stack với 03 services. Mỗi service đều nhân bản với 02 replicas. Trong đó service: web-nginx và web-phpfpm được phân bố ở node vnsys (là node manager); service db được phân bố vào node02 và node03 là các node worker.

Advertisements

Tổng quan về Docker Swarm

Posted: Tháng Mười Hai 6, 2018 in Container, Docker
Thẻ:,

Khi số lượng docker container, docker host tăng lên với số lượng lớn, việc triển khai, mở rộng và quản lý riêng lẻ từng container, docker host gây ra khó khăn. Ngoài ra, khi triển khai các ứng dụng bằng docker, chúng ta cũng có nhu cầu chạy cân bằng tải. Để giải quyết những vấn đề trên, chúng ta cần một số hệ thống giúp điều phối container để cho phép triển khai, mở rộng và quản lý được tự động theo cụm docker host.

Hiện tại có một số công cụ điều phối container phổ biến được sử dụng là: Swarm, Kubernetes và Amazon ECS

Trong phạm vi bài này chúng ta sẽ nói trước về Docker Swarm (Công cụ được tích hợp sẵn trong Docker)

1. Về Docker Swarm?

Docker swarm là công cụ điều phối docker container giúp nhóm các docker host riêng lẻ lại với nhau thành cluster.

Mỗi docker host kết nối đến tập hợp swarm đóng vai trò là manager hoặc worker và được gọi là một node.

  • Manager là node có nhiệm vụ quản lý và phân cấp các member. Nó là node được phép thực hiện các lệnh trong một swarm

  • Worker là node mà chạy chế độ swarm. Nó chỉ nhận và thực hiện các lệnh từ manager.

Một docker host có thể là manager, worker hoặc cả 2 chức năng. Khi docker chạy trong chế độ swarm, trên docker host đó vẫn có thể chạy các container độc lập

Một số cải tiến chính của Docker Swarm đó là:

  • Có thể sửa cấu hình service mà bao gồm network, volumes mà nó kết nối mà không cần restart service thủ công.
  • Docker sẽ tự động update cấu hình, dừng các task của service mà có cấu hình và tạo mới serice với cấu hình mong muốn.

Cũng như docker host chạy độc lập, docker swarm cũng có thể sử dụng docker compose để định nghĩa và chạy các container.

Service

Để triển khai một application trong chế độ Swarm, chúng ta tạo một service. Một service là định nghĩa các tasks để thực thi trên các node manager và worker.

Một service áp dụng một trong 2 mode sau:

– replicated mode: khi đó node manager sẽ phân phối lượng các tasks đến các node mà trong thiết lập cấu hình replica.

– global mode: Khi đó swarm chạy một task cho service trên mỗi node có sẵn trong cluster.

Task

Một task giữ thông tin về một docker container và các lệnh để chạy bên trong container đó. Khi một task được gán vào một node, nó không thể chuyển đến node khác.

2. Thiết lập docker swarm

Yêu cầu: docker phiên bản 1.12+ (từ version 1.12 tích hợp sẵn docker swarm)

Một swarm được tạo thành bởi nhiều node, có thể là vật lý hoặc ảo hóa (nhưng thực tế môi trường product là máy vật lý hoặc cloud).

Chúng ta thử tạo cluster với 01 node manager (192.168.10.111) và 02 node worker (192.168.10.112-113)

Step1: Cài đặt Docker trên các host

curl -sSL https://get.docker.com/ | sudo sh
sudo usermod -aG docker `whoami`
systemctl start docker.service
systemctl enable docker.service

Hoặc sử dụng script sau cho cài đặt: install_docker.sh

Step2: Mở ports trên firewall

Trước khi thực khởi tạo và thêm các node đến swarm chúng ta cần thực hiện mở port trên firewall để các docker host có thể giao tiếp với nhau

Trên manager node thực hiện mở các port sau:

  • TCP 2376/2377: Giao tiếp giữa các manager node
  • TCP/UDP 7946: Giao tiếp giữa các node
  • UDP 4789: Cho phép các node giao tiếp qua VXLAN

Nếu sử dụng firewalld thì thực mở như sau:

firewall-cmd --add-port=2376/tcp --permanent 
firewall-cmd --add-port=2377/tcp --permanent 
firewall-cmd --add-port=7946/tcp --permanent 
firewall-cmd --add-port=7946/udp --permanent 
firewall-cmd --add-port=4789/udp --permanent

Trên worker node thực hiện mở port sau:

  • TCP/UDP 7946
  • UDP 4789

Nếu sử dụng firewalld thực hiện mở như sau:

firewall-cmd --add-port=7946/tcp --permanent
firewall-cmd --add-port=7946/udp --permanent
firewall-cmd --add-port=4789/udp --permanent

Step3: Khởi tạo một swarm trên manager node

Trên docker host mà chúng ta dự định sẽ thành manager node, thực hiện lệnh khởi tạo sau:

docker swarm init

docker swarm init 
Swarm initialized: current node (18haxn9fy9d05foo48ysjw345) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-5xx36y3voshavvpd1n2j5zecjjg9m4e8gi9oqk99en9zd6vl4w-4n8pqcqal03mkfvce74c36rwz 192.168.10.186:2377

Sau khi khởi tạo thì docker host sẽ là manager node

Để docker host join vào một swarm thì chúng ta cần biết thông tin tokens trên manager node

– Show token cho các node managers

docker swarm join-token manager

docker swarm join –token SWMTKN-1-5xx36y3voshavvpd1n2j5zecjjg9m4e8gi9oqk99en9zd6vl4w-1ifnenemswb7zs4qa9ol59eft 192.168.10.186:2377

– Show token cho các node workers

docker swarm join-token worker
docker swarm join --token SWMTKN-1-5xx36y3voshavvpd1n2j5zecjjg9m4e8gi9oqk99en9zd6vl4w-4n8pqcqal03mkfvce74c36rwz 192.168.10.186:2377

Step4: Thêm các node đến swarm

Để thêm node đến swarm, cú pháp sẽ là:

​​docker swarm join [OPTIONS] HOST:PORT

Phần OPTIONS trong trường hợp này, chúng ta sẽ sử dụng token đã show ở trên

– Thêm node là worker

Trên docker hosts, chúng ta thực hiện lần lượt join đến swarm như sau:

docker swarm join \
--token SWMTKN-1-5xx36y3voshavvpd1n2j5zecjjg9m4e8gi9oqk99en9zd6vl4w-4n8pqcqal03mkfvce74c36rwz \
192.168.10.186:2377

This node joined a swarm as a worker.

Step5: Kiểm tra các nodes trong cluster

– Kiểm tra trạng thái Docker Swarm

docker info

– Show thông tin các node trong swarm

docker node ls

docker node ls

Chúng ta thấy có 01 node làm manager với status là Leader. 02 node còn lại là workers.

3. Deploy service

Để chạy một image trong chế độ swarm, chúng ta không sử dụng lệnh `docker run`, thay vào đó chúng ta sử dụng lệnh `docker service` và lệnh chỉ được thực hiện trên node manager.

3.1 Tạo một service đơn giản

Tạo một service có tên web_nginx, replica với 03 bản, publish với port 8080, mount ra ngoài

docker service create --name web_nginx --replicas 3 --publish 8080:80 \
--mount src=web_nginx,dst=/usr/share/nginx/html nginx

Xác nhận service “web_nginx” đang chạy

docker service ls

Show tasks của web_nginx service

docker service ps web_nginx

Ứng với mỗi replica, thì mỗi node sẽ tạo 01 container.

3.2 Update service

Trong khi service vẫn running, chúng ta có thể thực hiện update cấu hình service mà không cần phải stop hay tạo lại service

Syntax: `docker service update web_nginx [options]`

Sử dụng: `docker service update web_nginx –help` để xem các tùy chọn cần sử dụng

– Scaling service

Chúng ta scale service để mở rộng hoặc giảm bớt số lượng replicas khi service đang running

Ví dụ giảm replicas=02 cho web_nginx service ở trên như sau:

docker service update web_nginx --replicas 2

Hoặc sử dụng

docker service scale web_nginx=2

Note: 2 dòng lệnh trên là tương đương nhau, tuy nhiên có một sự khác biệt giữa sử dụng tham số –replicas và scale.

  • –replicas update số replicas trên một service
  • scale có thể update số lượng replicas trên nhiều service đồng thời

ví dụ:

docker service scale web_nginx=3 web_apache2=3

– Update mount

web_nginx service đã tạo ở trên, chúng ta đang sử dụng “mount” với type mặc định là “volume”. Để sử dụng mount với đường dẫn tuyệt đối (absolute path), chúng ta dùng type với “bind”.

Example:

docker service update web_nginx \
--mount-add type=bind,src=/web_nginx,dst=/usr/share/nginx/html

4. Roll back

Docker swarm cho phép chúng ta thực hiện hiện roll back khi update xảy ra lỗi. Chúng ta có thể cấu hình để thực hiện roll back thủ công hoặc tự động.

4.1 Thực hiện roll back thủ công

Cú pháp thực hiện roll back: `docker service rollback service_name`

Thực hiện roll back web_nginx service

docker service rollback web_nginx

 

docker service rollback web_nginx

4.2 Thực hiện roll back tự động

Một số tùy chọn khi thực hiện roll back

–rollback-delay 0s Thiết lập khoảng time mà task sau thực hiện roll back khi task trước thực hiện xong.
–rollback-failure-action pause Khi một task lỗi, whi đó nó sẽ pause/continue/rollback task tiếp theo
–rollback-max-failure-ratio 0 Thiết lập giá trị chấp nhận/ không chấp nhận lỗi với 0/1 khi có tỷ lệ lỗi trong quá trình roll back.
–rollback-monitor 5s Khoảng thời gian để monitor xem task roll back có ổn định không. Nếu ổn thì thì thực roll back task tiếp, nếu xảy ra lỗi trong khoảng time này thì quá trình roll back sẽ dừng
–rollback-parallelism 1 Số task thực hiện roll back đồng thời, mặc định là 1 task, thiết lập giá trị 0 thì all task thực hiện roll back đồng thời.

Chúng ta thực hiện tạo mới service web_apache, với các tùy chọn cho thực hiện roll back. Sau đó thực hiện quá trình update service để kiểm tra có xảy ra roll back tự động

Step1: Thực hiện tạo service web_apache

Thực hiện tạo service web_apache, và 2 task thực hiện roll back đồng thời, thời gian monitor là 20s cho service web_apache

docker service create --name web_apache --replicas 3 \
--update-failure-action rollback \
--mount type=volume,src=DocumentRoot_apache,dst=/usr/local/apache2/htdocs \
--rollback-parallelism 2 --rollback-monitor 20s httpd

Note: Mặc định tùy chọn “–update-failure-action rollback” có giá trị “pause”, vì vậy mà khi update service có lỗi thì nó sẽ dừng ở task đầu tiên và giữ nguyên trạng thái của các task còn lại. Để thực hiện roll back khi update xảy ra lỗi thì cần thiết lập tùy chọn chế độ roll back với “rollback”

Step2: Thực hiện update service

Thực hiện update service web_apache mà xảy ra lỗi, chẳng hạn update mount với thư mục không tồn tại với đường dẫn /DocumentRoot

docker service update web_apache \
--mount-add type=bind,src=/DocumentRoot,dst=/usr/local/apache2/htdocs

 

automatic rollback

Chúng ta thấy khi quá trình update xảy ra lỗi với service web_apache, khi đó quá trình rollback về phiên bản trước được thực hiện.

5. Rolling update

Tượng tượng rằng, khi triển khai một version mới của ứng dụng, chúng ta cần update ứng dụng đến toàn bộ task trong service. Cách thức đơn giản, là chúng ta sẽ stop service đó và tạo mới service từ một docker image được update. Khi đó, chúng ta thấy rằng cần downtime một lượng thời gian theo phương pháp update này.

Thay vì để downtime trong quá trình update, docker swarm cung cấp tính năng “rolling update”, mà cho phép update từng replica (task) trong khi service vẫn đang hoạt động.

Docker swarm sử dụng tính năng rolling update mặc định và cung cấp tùy chọn với 2 tham số:

  • update-delay: Thiết lập khoảng time ( mặc định là 0 giây; tùy chọn time có thể là giây Ts, phút Tm và giờ là Th) để thực hiện update task tiếp theo nếu quá trình update task trước đã xong và Running

  • update-parallelism: chỉ định số tasks tối đa (mặc định là 1) mà sẽ thực hiện update đồng thời

Qui trình để áp dụng rolling update mặc định như sau:

  • Stop task đầu tiên
  • Thực hiện update task vừa stop
  • Start container cho task vừa update
  • Nếu update cho một task mà trả lại trạng thái là “Running”, khi đó cho một khoảng time (dựa vào tham số update-delay) để thực hiện start task tiếp theo
  • Nếu trong quá trình update, một task trả lại trạng thái “Failed”, quá trình update sẽ được tạm dừng (pause) để đảm không xảy ra lỗi cho các task còn lại và duy trì trạng thái working cho service.
  • Chúng ta thực hiện update mà xảy ra trường hợp lỗi để kiểm tra
docker service update \
--mount-add src=/web_nginx,dst=/usr/share/nginx/html web_nginx

 

update service failed

Khi đó, chúng ta thấy ngay khi update task đầu tiên lỗi, quá trình update service tạm dừng

  • Thực hiện quá trình update service thành công
docker service update \
--mount-add src=web_nginx,dst=/usr/share/nginx/html \
--image nginx:latest web_nginx --update-delay 30s

 

update service success

Ta thấy task sau thực hiện update sau khoảng thời gian 30s khi task trước nó đã Running.

6. Draining nodes

Với các tùy chọn cấu hình mặc định, node manager sẽ thực hiện gán các task đến các node mà ACTIVE trong một swarm. Trong trường hợp, chẳng hạn khi cần dừng node nào đó để bảo trì, hay chúng ta remove một số node từ cluster. Khi đó, chúng ta sử dụng tính năng “drain” trong swarm để thực hiện.

Drain được sử dụng để ngăn một node nhận task mới từ manager và nó cũng dừng task mà đang chạy trên node đó.

Chúng ta kiểm tra các task trên các node trong swarm khi chưa sử dụng drain

before drain node

Khi đó chúng ta thấy các task được phân phối trên tất cả các node.

Cú pháp thực hiện drain node là: `docker node update –availability drain <NODE-ID>`

Chúng ta thực hiện drain “node02” trong swarm như sau:

docker node update --availability drain node02

Sau đó thực hiện check các task trong các service

after drain

Chúng ta thấy các task không chạy trên node02 và cũng thực hiện Shutdown các task đã chạy trước đó, đồng thời các task cũng được chuyển sang node khác.

Kiểm tra trạng thái của các node trong swarm

show node drain

node02 hiện tại STATUS vẫn là ready, nhưng thuộc tính “AVAIBILITY” đang là “Drain”, vì vậy nó không thể thực hiện nhận task từ manager.

Để đưa node02 trở lại thuộc tính “Active”, thêm thuộc tính “availibity” đến active

docker node update --availability active node02

Note: Nếu manager node ở thuộc tính “Drain” thì nó sẽ không nhận các task nhưng vẫn đóng vai trò là manager với nhiệm vụ quản lý, phân cấp các member và thực hiện các lệnh trong swarm.

7. Lập kế hoạch điều phối

Mặc định, các task được gán tự động đến các node mà có trạng thái Ready và Active trong swarm. Task sẽ được phân bố theo thứ tự node mà được tải ít nhất.

Trong nhiều trường hợp, chúng ta muốn tùy chọn phân bố số lượng replicas với node được chỉ định. Chẳng hạn chúng ta chỉ phân phối các task trên các node worker. Hoặc tùy thuộc hiệu năng của docker host, chúng ta điều phối task trên node được chỉ định. Khi đó, chúng ta sử dụng tùy chọn “constraint” trong docker swarm cho lập kế hoạch điều phối các node.

Một số trường hợp thực hành:

– Phân phối các task của service web_apache chỉ trên node worker

docker service create --name web_apache --replicas 3 \
--update-failure-action rollback \
--mount type=volume,src=DocumentRoot_apache,dst=/usr/local/apache2/htdocs \
--rollback-parallelism 2 --rollback-monitor 20s \
--constraint 'node.role == worker' httpd

hoặc nếu update

docker service update web_apache \
--constraint-add 'node.role == worker'

 

constraint with node role

–  Phân phối các task của service web_apache chỉ trên node03

docker service update web_apache \
--constraint-add 'node.hostname == node03'

 

Bài viết đã kết thúc phần giới thiệu tổng quan về docker swarm và các tính năng của nó. Trong bài tiếp sẽ thực hiện triển khai stack (như LEMP) trong docker swarm.

Tham khảo thêm về docker swarm tại: https://docs.docker.com/engine/swarm/

Mục tiêu là “Xây dựng hệ thống CI/CD sử dụng Jenkins multibranch pipeline and docker cho Laravel project”.

Mô hình chúng ta sẽ thực hiện

CICD Jenkins Docker

Chúng ta thực hiện các bước build 03 branches cho Laravel project  như sau:

  • Feature branch: Khi Developers commit code lên nhánh này thì Jenkins thực hiện các stage: check smc, cài đặt thư viện, chạy test trực tiếp trên Jenkins.

  • Develop branch: Khi accepted code vào develop branch, thì thực hiện các stage check scm, cài đặt thư viện, chạy test và đồng bộ code sang test server, sau đó thực hiện chạy ứng dụng trên môi trường test với docker containers

  • Master branch: Khi yêu cầu merge từ develop to master branch được accept, thì thực hiện các stage check scm, cài đặt thư viện, chạy test và đồng bộ code sang prod server, sau đó thực hiện chạy ứng dụng trên môi trường product với docker containers. Ở bước này, nếu có điều kiện chúng ta thực hiện thêm stage “integration test”

Yêu cầu trước khi thực hiện cấu hình:

  • Máy chủ Jenkins, cài đặt các PHP7 modules cho Testing
  • Máy chủ SCM (GitLab), kết nối webhook với Jenkins

  • Test Server: Cài đặt docker, docker-compose

  • Prod Server: Cài đặt docker, docker-compose

Tham khảo các bài viết trước trong chuyên mục: Full CICD

1. Tạo thư mục chứa project (code)

Thư mục project gồm: source code, Jenkinsfile, docker-compose.yml

Khi đó cấu trúc thư mục được xây dựng như sau

cicd jenkins docker directory tree

Nội dung trong cấu trúc thư mục như sau:

1.1 Jenkinsfile

Tệp tin Jenkinsfile được viết bằng scripted pipeline có nội dung sau:

node {
    currentBuild.result = "SUCCESS"
    try {
        stage('Checkout'){
            checkout scm
        }

        stage("Composer Update") {
        // Run composer update
            sh 'cd src && composer update'
        }

        stage("PHPUnit") {
            // Run PHPUnit
            sh 'cd src && ./vendor/bin/phpunit'
        }
        // Deploy to each branch
        switch (env.BRANCH_NAME) {
            case "master":
                stage("Build Docker"){
                    sh 'rsync -avzhP --delete --exclude=.git/ --exclude=Jenkinsifle $WORKSPACE/ root@192.168.1.111:/root/docker/'
                    //build docker & mount source to /var/www/html
                    sh 'ssh root@192.168.1.111 "cd /root/docker/ && bash docker-compose.sh"'
                    //push image to private dockerhub
                    //....
                }
                break
            case "develop":
                stage("Build Docker"){
                    sh 'rsync -avzhP --delete --exclude=.git/ --exclude=Jenkinsifle $WORKSPACE/ root@192.168.1.112:/root/docker/'
                    //build docker & mount source to /var/www/html
                    sh 'ssh root@192.168.1.112 "cd /root/docker/ && bash docker-compose.sh"'
                }
                break
            default:
        // No deployments for other branches
                break
        }
    }
    catch (err) {
            currentBuild.result = "FAILURE"
                mail body: "project build error is here: ${env.BUILD_URL}" ,
                from: 'info@vnsys.wordpress.com',
                replyTo: 'info@vnsys.wordpress.com',
                subject: 'project build failed',
                to: 'keepwalking86@vnsys.wordpress.com'
            throw err
        }    
}

Trong script này, chúng ta sử dụng biến “currentBuild.result” để xác định kết quả trạng thái build là “SUCCESS” hay “FAILURE”

Sử dụng try/catch block. Trong đó chúng ta sử dụng “try” block để định nghĩa 4 stage đơn giản sau:

  • Checkout: sẽ kiểm tra source code từ GitLab và pull project từ gitlab có địa chỉ http://gitlab.example.local/dungnv/laravel-docker-compose.git
  • Composer_Update: sẽ thực hiện lệnh “composer update”
  • PHPUnit: Sẽ thực hiện lệnh phpunit cho để test source code. Tùy thuộc vào nhu cầu và điều kiện chúng ta sẽ thực hiện chi tiết và nhiều hơn các bước test. Trong bài viết này, chúng ta chỉ sử dụng PHPUnit cho test đơn giản
  • Build Docker: Sẽ thực hiện đồng bộ code sang server (test/prod), tùy thuộc vào điều kiện kiểm tra. Nếu source code trên develop branch, khi đó nó sẽ đồng bộ sang test server và sau đó chạy script docker-composer.sh để thực hiện build docker images và chạy các container. Nếu source code trên master branch, khi đó code sẽ được đồng bộ sang production server và chạy docker-composer.sh để thực hiện build docker images và chạy các container. Đối với các nhánh khác thì chỉ thực hiện 3 bước đầu tiên.
  • Khối “catch” được sử dụng khi biến “ currentBuild.result” xác định kết quả trạng thái build “FAILURE”, khi đó nó thực hiện gửi thông báo build lỗi qua mail

1.2 ./docker Directory

Thư mục ./docker chứa thông tin cấu hình để build các image: nginx và php-fpm

  • Nội dung tệp Dockerfile cho nginx docker image
FROM nginx:latest

MAINTAINER KeepWalking86

ADD ./default.conf /etc/nginx/conf.d/
RUN mkdir -p /var/www/example/
#ADD ./src /var/www/example/public/
RUN chown -R nginx:nginx /var/www/example

#Install net tools
RUN apt-get update
RUN apt-get install net-tools lsof -y

#Change nginx uid & gid
RUN usermod -u 1000 nginx && groupmod -g 1000 nginx

##Clearing the apt-get caches
RUN apt-get clean

## Expose ports
EXPOSE 80
  • Ngoài ra ta sẽ add tệp cấu hình default nginx cho site (ví dụ example.local)
server {
        listen 80;
        listen [::]:80 ipv6only=on;

    # Log files for Debugging
        access_log /var/log/nginx/laravel-access.log;
        error_log /var/log/nginx/laravel-error.log;

    # Webroot Directory for Laravel project
        root /var/www/example/public;
        index index.php index.html index.htm;

        # Your Domain Name
        server_name example.local;
        location ~* \.(jpg|jpeg|gif|css|png|js|ico|txt|srt|swf|zip|rar|html|htm|pdf)$ {
                access_log        off;
                log_not_found     off;
                expires 30d; # caching, expire after 30 days
        }

        location / {
                try_files $uri $uri/ /index.php?$query_string;
        }

    # PHP-FPM Configuration Nginx
        location ~ \.php$ {
                try_files $uri =404;
                fastcgi_split_path_info ^(.+\.php)(/.+)$;
                #fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
                fastcgi_index index.php;
                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                include fastcgi_params;
                fastcgi_pass php-fpm:9000;
        }
}
  • Nội dung tệp tin Dockerfile cho php-fpm docker image
FROM php:7.2-fpm

RUN apt-get update
RUN apt-get install libxml2-dev unzip libmcrypt-dev zlib1g-dev -y
#Using docker-php-ext-install to install php modules
RUN docker-php-ext-install pdo_mysql mysqli mbstring xml opcache zip

EXPOSE 9000
CMD ["php-fpm", "-F"]

1.3 Tệp Compose (docker-compose.yml)

Chúng ta tạo tệp tin docker-compose.yml để định nghĩa các service và kết hợp chúng với nhau để chạy ứng dụng. Tệp tin được định nghĩa gồm 03 service: db, php-fpm và webapp

version: "3"

services:
    db:
      image: mariadb:latest
      volumes:
        - ./db:/var/lib/mysql/laravel
      restart: always
      environment:
        MYSQL_ROOT_PASSWORD: P@ssw0rd
        MYSQL_DATABASE: laravel
        MYSQL_USER: keepwalking
        MYSQL_PASSWORD: P@ssw0rd

    php-fpm:
      build: ./docker/php-fpm
      volumes:
        - ./src:/var/www/example/

    webapp:
      depends_on:
        - db
        - php-fpm
      build: ./docker/nginx
      ports:
        - 8081:80
      restart: always
      volumes:
        - ./src:/var/www/example/

1.4 Tệp bash script (docker-compose.sh)

Chúng ta tạo tệp tin docker-compose.sh cho phép chạy tự động để kiểm tra điều kiện port, container và gọi tệp tin docker-compose.yml

#!/bin/bash
PORT="8081"

#Check port exists
netstat -nta |grep -i listen |grep $PORT &>/dev/null
if [[ $? -eq 0 ]]; then
    echo "Port ${PORT} is existing. Please, use different ports for Nginx"
    exit 0;
else
    echo "Creating docker containers for NGINX MARIADB PHP7.2"
    sleep 2
    docker-compose up -d
fi

1.5 ./src directory

Cuối cùng là thư mục ./src mà chứa dữ liệu source code (ở đây sử dụng Laravel framework) để cho phép bind sourcecode giữa docker host và container.

Source code chúng ta có thể download tại https://github.com/laravel/framework

3.2 Tạo repository trên GitLab

Chúng ta sẽ tạo repository trên GitLab với tên “laravel-docker-compose” khi đó đường dẫn repository là: http://gitlab.example.local/dungnv/laravel-docker-compose.git

Thực hiện push project lên GitLab repository

Truy cập thư mục chứa project, thực hiện đẩy lên gitlab repository “multibranch_pipeline_docker”

git init
git remote add origin http://gitlab.example.local/dungnv/laravel-docker-compose.git
git add  .
git commit -m "Add repo"
git push origin master

Sau đó chúng ta thực hiện tạo thêm một branch “develop

develop branch

3.3 Tạo Job (item) trên Jenkins

Thực hiện tạo Job (item) “multibranch_pipleline_docker” trên Jenkins

Tại thẻ Branch Sources → vào thông tin đường dẫn repo “http://gitlab.example.local/dungnv/laravel-docker-compose.git” → Chọn Credentials

Tại thẻ Build Configuration → Sử dụng với tệp Jenkinsfile với đường dẫn gốc của repository.

→ nhấn Save để lưu lại Jenkins job.

laravel docker compose jenkins job

a. Kiểm tra build trên Jenkins

Truy cập giao diện web của Jenkins, khi đó nó scan ra được 2 branch: master và develop. Quá trình build cũng bắt đầu được thực hiện

scan multibranch pipeline log

Chúng ta truy cập vào từng branch vào xem kết quả

Trên nhánh master

  • Xem chi tiết kết quả build qua console output

master console output

  • Xem kết quả build qua stage view

pipeline master

Trên nhánh develop

  • Xem kết quả build qua console output

develop console output

  • Xem kết quả build qua stage view

pipeline develop

b. Kiểm tra trên 02 Server (Test/Prod)

  • Trên Prod Server

docker containers on prod

  • Trên Test Server

docker containers on test server

Chúng ta thấy docker-compose đã tạo 03 containers ở mỗi Test/Prod server cho chạy ứng dụng Laravel.

Ở các phần trước ta cấu hình đơn giản bằng giao diện, và thực hiện một số bước build. Trong phần này, chúng ta thực hiện triển khai CI/CD với Jenkins Pipeline cho PHP.

I. Về Jenkins pipeline

1. Jenkins pipeline là gì?

Pipeline là tập hợp các task mà kết nối và tích hợp với nhau theo tuần tự. Tuần tự nghĩa là task sau chỉ thực hiện khi task trước nó thực hiện thành công

pipeline

Jenkins Pipeline là một bộ plugin mà hỗ trợ việc triển khai và tích hợp phân phối liên tục theo kiểu pipeline.

Pipepine phân phối liên tục (Continuous Delivery hay CD) gồm các task được xử lý theo quy trình tự động trong quá trình phát triển phần mềm. Các task trong pipeline CD gồm: Checkout, Build, Test, Staging và Deploy

Flowwork CICD with Jenkins

Jenkins pipeline cung cấp bộ công cụ mở rộng cho mô hình CD pipeline từ đơn giản, đến phức tạp. Định nghĩa Jenkins pipeline thường được viết vào một tệp text (Tệp này được gọi là Jenkinsfile).

2. Define Jenkins Pipeline

Chúng ta có thể tạo một pipeline theo các cách sau:

Through Blue Ocean – Là cách đơn giản cho tạo Pipleline bằng giao diện Blue Ocean (Để sử dụng chúng ta cài thêm plugin Blue Ocean trên Jenkins). Blue Ocean giúp viết Jenkinsfile và commit nó đến source control

  • Pipeline script (web UI): Vào trực tiếp script trên giao diện

  • Pipeline script from SCM: Nội dung tệp script sẽ được cấu hình trong tệp tin Jenkinsfile (mặc định được đặt trong thư mục chính chứa project trên Jenkins)

Về script, thì pipeline hỗ trợ 2 kiểu cú pháp:

– Declarative Pipleline (được giới thiệu từ pipleline 2.5): Trình bày kiểu cú pháp statements và expressions cơ bản, có cùng các rule giống như kiểu cú pháp của “Groovy”

Một Declarative Pipeline bắt buộc phải nằm trong khối pipeline { .. }, ví dụ:

pipeline {
 /* insert Declarative Pipeline here */
}

Một mẫu Jenkinsfile viết theo cú pháp Declarative

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                echo 'Building..'
            }
        }
        stage('Test') {
            steps {
                echo 'Testing..'
            }
        }
        stage('Deploy') {
            steps {
                echo 'Deploying....'
            }
        }
    }
}

– Scripted Pipeline: Sử dụng kiểu cú pháp của Groovy, mà cho phép ta tạo nên script linh hoạt trong hệ thống phân phối liên tục. Một scripted pipeline có thể gồm: phần kiểm tra điều kiện, các vòng lặp, các khối try/catch/finally và thậm chí là các functions.

Một ví dụ của scripted pipeline:

node {
    stage('Example') {
        if (env.BRANCH_NAME == 'master') {
            echo 'I only execute on the master branch'
        } else {
            echo 'I execute elsewhere'
        }
    }
}

Có một sự khác biệt nữa giữa Declarative and Scripted pipeline là: Thay vì chạy tuần tự xử lý một số tác vụ trong một stage (tất nhiên trong trường hợp các task đó không phụ thuộc nhau), thì ở scripted pipeline cho phép chúng ta thực thi các task đó một cách đồng thời (chế độ parallel). Ví dụ:

stage('Build') {
    /* .. snip .. */
}
stage('Test') {
    parallel linux: {
        node('linux') {
            checkout scm
            try {
                unstash 'app'
                sh 'make check'
            }
            finally {
                junit '**/target/*.xml'
            }
        }
    },
    windows: {
        node('windows') {
            /* .. snip .. */
        }
    }
}

Khi đó thay vì chạy kiểm tra tuần tự linux xong, rồi đến kiểm tra windows, chúng ta đã thực hiện chạy kiểm tra đồng thời cả linux và windows.

Tham khảo thêm tại:

https://jenkins.io/doc/book/pipeline/jenkinsfile/

https://jenkins.io/doc/book/pipeline/syntax/

II. CICD với Jenkins pipeline cho PHP Laravel

1. Cài đặt PHP, composer

Để thực hiện quá trình CI with Jenkins và PHP, chúng ta cần cài đặt PHP và các modules PHP liên quan

Chúng ta có thể thực hiện cài đặt đồng thời nhiều version PHP trên Jenkins server. Trong hướng dẫn cài đặt này Jenkins tôi đang sử dụng trên Ubuntu

– Add repository

sudo apt install python-software-properties
sudo add-apt-repository ppa:ondrej/php
sudo apt-get update

Tùy thuộc vào từng dự án, mà yêu cầu các sử dụng phiên bản, cũng như các modules PHP khác nhau, khi đó chúng ta cần cài đặt phù hợp với yêu cầu.

– Cài đặt PHP5.6, PHP7.0, PHP7.1

sudo apt install php5.6 -y
sudo apt install php7.0 -y
sudo apt install php7.1 -y

– Cài đặt PHP modules

sudo apt-get install -y php5.6 php5.6-xdebug php5.6-xsl php5.6-domphp5.6-zip php5.6-mbstring 
sudo apt-get install -y php7.0 php7.0-xdebug php7.0-xsl php7.0-domphp7.0-zip php7.0-mbstring 
sudo apt-get install -y php7.1 php7.1-xdebug php7.1-xsl php7.1-dom php7.1-zip php7.1-mbstring

– Cài đặt Composer

sudo curl -sS https://getcomposer.org/installer |php -- --install-dir=/usr/bin –filename=composer

2. Pipeline với Laravel

Chúng ta thử tạo một Jenkins job đơn giản sử dụng pipeline cho Laravel Framework

2.1 Tạo project Laravel trên GitLab

B1: Chúng ta thực hiện tạo project “laravel” trên GitLab. Khi đó đường dẫn project là: http://gitlab.example.local/jenkin/laravel.git

B2: Chúng ta sẽ download souce code từ: https://github.com/laravel/framework

Sau đó push source code lên “http://gitlab.example.local/jenkin/laravel.git

2.2 Thực hiện cấu hình Pipeline trên Jenkins

Tạo một project đơn giản mà thực hiện một số công việc sau:

  • Check out the latest build of laravel/framework

  • Install & update Composer dependencies

  • Run Unit test với PHPUnit

Các bước thực hiện như sau:

Truy cập giao diện Jenkins URL: http://192.168.1.221:8080/view/all/newJob → sau đó vào tên job là “laravel” và chọn “Pipleline” → Nhấn OK để tạo Job với Pipleline

laravel pipeline

Khi đó chúng ta sẽ redirect sang trang configuration job → Tại thẻ “Pipleline” chúng ta sẽ thiết lập pipeline bằng script. Có 2 tùy chọn definition mà chúng ta đã nói ở trên, chúng ta chọn “Pipeline script”

scripted pipeline

  • Bây giờ chúng ta vào thông tin script sau trong phần script của pipeline cho Laravel project
node {
    stage('preparation') {
        // Checkout the master branch, with credential
        git credentialsId: 'jenkin', url: 'http://gitlab.example.local/jenkin/laravel.git'
    }
    stage("composer_install") {
        // Run `composer install`
        sh 'composer install'
    }
    stage("phpunit") {
        sh 'vendor/bin/phpunit'
    }
}

Ở đây chúng ta tạo script gồm 3 stage đơn giản:

  • preparation: sẽ kiểm tra master branch và pull project từ gitlab có địa chỉ “http://gitlab.example.local/jenkin/laravel.git“, với thông tin xác thực người dùng là “jenkin”

  • composer_install: sẽ thực hiện lệnh “composer install”

  • phpunit: Sẽ thực hiện lệnh phpunit cho để check unit test code

Chúng ta save scripted pipeline và thực hiện build job. Khi đó nó hiển thị trực quan các stage trong quá trình build

stage view

Chúng ta có xem chi tiết bằng text với “Console output”

...............................................................  63 / 255 ( 24%)
............................................................... 126 / 255 ( 49%)
............................................................... 189 / 255 ( 74%)
............................................................... 252 / 255 ( 98%)
...

Time: 25.27 seconds, Memory: 22.00MB

OK (255 tests, 612 assertions)

Generating code coverage report in Clover XML format ... done

Generating code coverage report in HTML format ... done
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

3. Multibranch Pipeline với Laravel

Trong hầu hết các dự án, trong một repository thì chúng ta đều tạo ra nhiều branches để định nghĩa nhiều stages cho project đó

Hầu hết chúng ta phân ra 3 branches sau:

  • master: Code trong nhánh này sẽ được dùng cho production

  • develop: Code trong nhánh này sẽ được dựng thử

  • feature: Chứa code trong quá trình development

Trong đó, nhánh master và develop sẽ thực hiện chạy cả test unit và integration test và thực hiện deploy code đến server: dev và prod. Còn nhánh còn lại thì chỉ chạy unit test

Note: Trong phạm vi bài viết này tôi sẽ lấy trực tiếp Laravel Framework trên base repository, vì vậy chỉ thực hiện chạy unit test với phpunit. Chúng ta có thể sử dụng  Apache Ant để tự động hóa quá trình nhiều bước test hơn với một số công cụ như: phpcs, phpcpd, ..

3. 1. Trên GitLab

Thừa kế từ Laravel Repository đã tạo ở trên, chúng ta tạo thêm branch mới là “develop”

Thực hiện tạo các branchs như sau:

Mở laravel project trên GitLab → Branch → New Branch → vào thông tin branch mới “develop” → Create branch.

develop branch

Tiếp theo chúng ta sẽ tạo tệp tin “Jenkinsfile” và push lên laravel repository.

Nội dung Jenkinsfile như sau:

node {
    currentBuild.result = "SUCCESS"
    try {
        stage('Checkout'){
                checkout scm
            }
            stage("composer_update") {
                // Run composer update
                sh 'composer update'
            }
            stage("unittest") {
                // Run PHPUnit
                sh 'vendor/bin/phpunit'
            }
            // Create new deployment assets
            switch (env.BRANCH_NAME) {
                case "master":
                    stage("deploycode") {
                        sh 'rsync -avzhP --delete --exclude=.git/ --exclude=Jenkinsifle $WORKSPACE/ root@192.168.1.112:/var/www/example.local/'
                        sh 'ssh root@192.168.1.112 "chown -R nginx:nginx /var/www/example.local"'
                    }
                    break
                case "develop":
                    stage("deploycode") {
                        sh 'rsync -avzhP --delete --exclude=.git/ --exclude=Jenkinsifle $WORKSPACE/ root@192.168.1.111:/var/www/html/'
                        sh 'ssh root@192.168.1.111 "chown -R nginx:nginx /var/www/html"'
                    }
                    break
                default:
                    // No deployments for other branches
                    break
            }
    }
    catch (err) {
            currentBuild.result = "FAILURE"
                mail body: "project build error is here: ${env.BUILD_URL}" ,
                from: 'info@vnsys.wordpress.com',
                replyTo: 'info@vnsys.wordpress.com',
                subject: 'project build failed',
                to: 'keepwalking86@vnsys.wordpress.com'
            throw err
        }    
}

Trong script này
Chúng ta sử dụng biến “currentBuild.result” để xác định kết quả trạng thái build là “SUCCESS” hay “FAILURE”

Sử dụng try/catch block. Trong đó

  • Chúng ta sử dụng “try” block để định nghĩa 4 stage đơn giản sau:

  • checkout: sẽ kiểm tra source code từ GitLab và pull project từ gitlab có địa chỉ http://gitlab.example.local/jenkin/laravel.git

  • composer_update: sẽ thực hiện lệnh “composer update”

  • unittest: Sẽ thực hiện lệnh phpunit cho để check unit test code

  • deploycode: Sẽ thực hiện đồng bộ code sang server (test/prod), tùy thuộc vào điều kiện kiểm tra. Nếu source code trên develop branch, khi đó nó sẽ đồng bộ sang test server. Nếu source code trên master branch, khi đó code sẽ được đồng bộ sang production server. Đối với các nhánh khác thì chỉ thực hiện 3 bước đầu tiên.

  • Khối “catch” được sử dụng khi biến “ currentBuild.result” xác định kết quả trạng thái build “FAILURE”, khi đó nó thực hiện gửi thông báo qua mail

3.2 Cấu hình trên Jenkins

Trên giao diện Jenkins, chúng ta tạo một job mới với tên “laravel_multibranch” → OK

laravel multibranch pipeline

Khi đó sẽ chuyển giao diện cấu hình job.

Tại tab “Branch Sources”, chúng ta thực hiện:

  • Chọn “git” và vào thông tin kết nối đển repository của GitLab (hoặc SCM mà mình dùng).

Trong trường hợp này mình vào thông tin Project Repository là “http://gitlab.example.local/jenkin/laravel.git” .

  • Chọn “Credentials”

  • Tại Behaviors, ta sẽ chọn giá trị “Discover branches” (Nghĩa là nó sẽ tìm các branches có tên SCM GitLab)

branch sources for multiple pipeline

Tại tab “Build Configuration”: Chế độ sẽ build là “Jenkinsfile”, vào thông tin đường dẫn script Jenkinsfile (mặc định nằm ở thư mục gốc của code)

build configuration for multibranch pipeline

→ Cuối cùng nhấn Save để lưu job “laravel_multibranch”
Khi đó hệ thống sẽ tự động Scan các branch và tìm được 02 branches: develop và master như hình dưới

Scan multibranch pipeline log

Sau khi tìm được 02 branches develop và master, ta thấy ở trạng thái build cũng đang thực hiện quá trình build.

Quay ra giao diện quản lý các jobs

Jenkins items

Chúng ta click vào job “laravel_multibranch”, khi đó chúng ta sẽ thấy trạng thái build của các branches.

laravel_multibranch

Chúng ta vào từng branch và có thể xem kết quả build trực quan các bước build

multibranch pipeline master

Kết quả trực quan, khi chỉ ra quá trình build với 4 stages: checkout, composer_update, unitest, deploycode. Cũng như thời gian thực hiện của mỗi stage

Chúng ta có thể xem thông tin build qua chế độ text với Console Output

View output console

Chúng ta thấy sau khi kiểm tra unit test thành công thì code sẽ được deploy qua web server (192.168.1.112 sử dụng Nginx)

3.3 Build khi nhận Trigger từ GitLab webhook

Để thực hiện tự động build khi push hay merge code lên các branches, chúng ta cần tạo webhook trên GitLab.

Tham khảo cách cấu hình webhooks trên GitLab ở bài viết Cấu hình WebHooks trên GitLab

Step1: Thực hiện tạo webhook trên GitLab

Mở Laravel project trên GitLab → Settings → Integrations → Vào thông tin kết nối đến Job trên Jenkins ( laravel_multibranch).

Đường URL thường là {http/https}://jenkins_address_ip:8080/project/jenkins_job

Khi đó, chúng ta vào URL đầy đủ như sau:

http://192.168.1.221:8080/project/laravel_multibranch

  • Tại phần Triggers, chọn giá trị “Push Events” và “Merge request events”

Sau đó nhấn “Add Webhook” để tạo webhook

jenkins gitlab webhook for multibranch

Step2: Chúng ta thực hiện nhanh quá trình đẩy code lên branch mới trên gitlab

push code to dungnv branch

Ở đây tôi sẽ push code lên nhánh dungnv, bởi vì tôi muốn biết trên Jenkins lúc đó sẽ thực hiện các task gì.

Step3: Sau khi push code lên gitlab, chúng ta kiểm tra quá trình thực hiện trên Jenkins

pipeline dungnv

Chúng ta thấy có quá trình thực hiện build trên nhánh code “dungnv”, và chỉ thực hiện build với 03 stage: Checkout, composer_update, unittest. Ở nhánh này, không build stage “deploycode” như 2 nhánh còn lại develop và master

Như vậy là chúng ta kết thúc phần cấu hình CICD với Jenkins Pipeline cho Laravel Framework

Dựng Private Docker Registry

Posted: Tháng Mười Một 5, 2018 in Docker
Thẻ:

Thay vì pull, push các docker images từ Docker Registry public, chúng ta có thể xây dựng Private Docker Registry. Private Docker Registry cho phép chúng ta chứa images dùng riêng tư trong phạm vi của công ty, ngoài ra cải thiện băng thông khi tốc độ truy cập đến các public docker registry có vấn đề.

private-docker-registry

1. Cài đặt Docker Registry cho localhost

Trên Server mà cài đặt docker và docker-compose, chúng ta thực hiện triển khai cài đặt docker register như sau:

  • Tạo thư mục để lưu các images

sudo mkdir -p /srv/registry

  • Start registry container

docker run -d -p 5000:5000 -v /srv/registry:/var/lib/registry –restart=always –name registry registry:2

Lệnh trên sẽ thực hiện: expose cổng từ container ra docker host với port 5000

Nếu chúng ta muốn thay port mà listen on trên container (mặc định 5000), chúng ta có thể sử dụng biến môi trường REGISTRY_HTTP_ADDR để thay giá trị port 5000. ví dụ ta đổi port 5000 thành 5005 như sau:

docker run -d -e REGISTRY_HTTP_ADDR=0.0.0.0:5005 -p 5000:5005 -v /srv/registry:/var/lib/registry –restart=always –name registry registry:2

2. Cài đặt Docker Registry dùng local network

Nếu chỉ cài đặt docker registry cho localhost, thì phạm vi sử dụng image chỉ dành riêng cho host đó. Để cho phép sử dụng Docker Registry trong phạm vi toàn mạng, chúng ta cần thiết lập thêm một số tùy chọn.

Step1: Thiết lập certificate cho docker registry

Nếu không có cert SSL public, chúng ta có thể cài đặt ở chế độ self-signed certificate(sử dụng ở chế độ insecure registry)

Đăng ký cert SSL cho tên miền: example.local

  • Tạo tệp tin /etc/docker/daemon.json với nội dung sau
{   "insecure-registries" : ["hub.example.local:5000"] }

Tạo self-signed certificates

sudo mkdir -p /etc/certs
sudo openssl req \
  -newkey rsa:4096 -nodes -sha256 -keyout /etc/certs/hub.example.local.key \
  -x509 -days 365 -out /etc/certs/hub.example.local.crt

Note: khi tạo self-signed certs, thì phần Common Name, chúng ta điền tên domain vào. “Common Name (e.g. server FQDN or YOUR name) []:hub.example.local

Khi đó chúng ta có key hub.example.local.key và cert hub.example.local.crt

  • Stop registry nếu đang chạy
docker container stop registry

Step2: Start registry container

Chúng ta thiết lập biến môi trường, để chỉ định đường dẫn chứa các tệp tin hub.example.local.crt và hub.example.local.key . Registry chạy trên https mặc định với port 443

docker run -d --restart=always --name registry \
-v /etc/certs:/certs -v  /srv/registry:/var/lib/registry \
-e REGISTRY_HTTP_ADDR=0.0.0.0:443 \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/hub.example.local.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/hub.example.local.key \
-p 443:443 registry:2

3. Update cert trên Docker Client

Để docker client có thể truy cập và pull/push các image, khi đó nó cần add cert từ docker registry.

Thực hiện copy tệp “hub.example.local.crt” từ Docker Registry đến các docker client.

Thực hiện update lại các cert trên docker client

Với Docker Client trên Ubuntu

sudo cp /path/to/hub.example.local.crt
/usr/local/share/ca-certificates/hub.example.local.crt
sudo update-ca-certificates

Với CentOS/RedHat

sudo cp /path/to/hub.example.local.crt
/etc/pki/ca-trust/source/anchors/hub.example.local.crt
sudo update-ca-trust

Sau khi add certificates thực hiện restart docker

sudo systemctl restart docker

– Troubleshoot insecure registry

Khi chứng thực, các phiên bản docker yêu cầu trust certificate ở cấp độ OS. Khi đó chúng ta sẽ add cert đến các docker client và update lại certificates

Note: là certificate sẽ được add trên các docker client (như Jenkins mà chúng ta sẽ sử dụng trong CICD)

4. Pull, Push images với Docker Registry

Chúng ta thực hiện copy images từ Docker Hub vào kho images Docker Registry (hoặc thực hiện trên Docker client). Sau đó thực hiện kiểm tra pull và push images

docker pull ubuntu:16.04
docker tag ubuntu:16.04 hub.example.local/ubuntu:16.04
docker push hub.example.local/ubuntu:16.04

Note: Chúng ta có thể thực hiện pull/push trên docker registry hoặc docker client cho các bước như trên

5. Docker-Registry với chứng thực

Chúng ta sử dụng công cụ htpasswd để tạo tệp tin mà lưu trữ thông tin người dùng cho chứng thực basic

Step1: Cài đặt công cụ htpasswd để add users

Trên CentOS

yum -y install httpd-tools
  • Trên Ubuntu
sudo apt-get install apache2-utils

Step2: Generate mật khẩu chứng thực cho users

Chúng ta sẽ tạo tệp tin mà lưu trữ thông tin chứng thực người dùng vào /etc/docker/.htpasswd

Thực hiện thông tin chứng thực như sau:

syntax: sudo htpasswd -Bc /etc/docker/.htpasswd username

keepwalking@vnhack:~$ sudo htpasswd -Bc /etc/docker/.htpasswd keepwalking
New password: 
Re-type new password: 
Adding password for user keepwalking

Với thông tin tham số như sau:

  • B: Để mã hóa thông tin mật khẩu với “bcrypt” (mặc định là MD5)

  • c: Để tạo tệp tin chứa mật khẩu (Chúng ta sử dụng tham số này lần đầu khi tạo tệp tin password)

Step3: Đăng nhập Docker Registry với chứng thực

keepwalking@vnhack:~$ docker login hub.example.local
Username: keepwalking
Password: 
WARNING! Your password will be stored unencrypted in /home/keepwalking/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

 

Xem thêm nội dung bài viết tại:  https://docs.docker.com/registry/deploying/