Áp dụng CI/CD vào project của bạn (P2)

1. Mở đầu


Ở bài viết trước mình đã giới thiệu cho các bạn về CircleCI cũng như cách setup project của mình trên CircleCI. Trong bài phần này mình sẽ hưỡng dẫn các bạn nốt hai phần còn lại đó là NotificationAuto Deploy. Nào chúng ta cùng bắt đầu 😄.

2. Notification


Với một bản build mà chúng ta vừa thực hiện ở trên, thời gian mỗi bản build chỉ có vài chục giây nên ta hoàn toàn có thể ngồi đợi bản build đó chạy xong và xem kết quả. Tuy nhiên với project lớn hơn, thời gian build sẽ có thể lên đến vài phút thậm chí vài chục phút mà chúng ta cứ nhìn ngồi nhìn màn hình thì sẽ hơi tốn thời gian. Thay vào đó, ta có thể sử dụng tính năng notification để gửi thông báo mỗi khi có bản build thành công hoặc thất bại để trong lúc chờ bản build ta có thể làm việc khác. Ở đây mình sẽ hướng dẫn các bạn gửi notification qua 2 kênh là SlackChatwork.

2.1 Slack

Đối với việc gửi notification qua slack khá đơn gian vì CircleCi đã hỗ trợ nó rồi. Để config notification cho Slack, ở màn hình danh sách các bản build bạn chọn icon hình bánh răng để vài phần setting cho project của bạn:

Tiếp đỏ ở thanh menu bên tay phải bạn chọn phần Chat Notifications trong mục NOTIFICATIONS. Như bạn có thể thấy CircleCI đã hỗ trợ một số ứng dụng khác trong đó có Slack:

Bạn lấy thông tin channelwebhook từ bên slack của bạn (hướng dẫn ở đây đây) và sau đó điền các thông tin đó vào CircleCI. Sau đó ta chọn Save và thử gửi 1 pull/push request sẽ nhận được kết quả như sau:

2.2 Chatwork

Mặc dù CircleCI không hỗ trợ Chatwork sẵn tuy nhiên, do mình làm việc chủ yếu trên Chatwork nên mình cũng đã tìm cách để có thể gửi được notification lên chatwork. Mục tiêu mà chúng ta hướng đến cũng giống như với bên Slack là có thể thu nhận lại được một số thông tin về bản build như sau:

  • Tên repository
  • Người tạo bản build
  • Tên branch
  • Link dẫn tới commit/pullrequest đó trên github (hoặc bitbucket)
  • Link dẫn tới bản build đó trên CircleCI Để làm được điều này, chúng ta sẽ sử dụng API mặc định của Chatwork cung cấp. Cụ thể bạn có thể tham khảo danh sách các API ở đây. Để có thể thao tác được với API của Chatwork, trước tiên chúng ta cần có một tài khoản Chatwork và lấy API từ tài khoản đó để sử dụng. Mình đã tạo sẵn thêm 1 tài khoản Chatwork khác và sử dụng token của nó, bạn có thể lấy token bằng cách bấm vào phần tên của mình ở góc trên bên trái trong Chatwork và chọn API setting sau đó nhập lại password để lấy được token.

Sau khi đã lấy được Chatwork token, ta sẽ thêm nó vào danh sách các biến môi trường để có thể sử dụng trong file config.yml mà vẫn đảm bảo được tính bí mật. Đầu tiên ta vào phần setting của project trên CirclCI:

Tiếp đó trong trang setting, ta chọn mục Enviroment Variables và chọn Add Variable:

Cuối cùng ta nhập tên của biến đi kèm với đó là giá trị tương ứng và chọn Add Variable để lưu lại biến:

Với mỗi biến mà ta khai báo ở đây ta có thể sử dụng chúng trong file config.yml bằng cách sử dụng tên biến bạn đặt kết hợp với kí tự $ ở đầu. Ví dụ ở trên mình đặt tên biến là CHATWORK_TOKEN thì trong file config.yml sẽ sử dụng là $CHATWORK_TOKEN. Chú ý bạn nên đặt tên các biến môi trường dưới dạng chữ hoa để tránh nhầm lẫn với các lệnh khác trong file config.yml. Tiếp sẽ tạo 1 room chat mới với tài khoản chính của mình và tài khoản vừa tạo ở trên rồi lấy id của phòng đó khai báo tiếp trong phần Enviroment Variables. Đây là kết quả sau khi mình đã tạo xong 2 biến môi trường:

Tiếp tới chúng ta sẽ mở lại file config.yml trong project của chúng ta lên và thêm phần sau vào cuối file:

...
# Send notification to chatwork
- run:
    name: Sending notification
    command: |
      if [[ true ]];  then
        curl -X POST -H "X-ChatWorkToken: $CHATWORK_TOKEN" -d \
          "body=Test message" \
          "https://api.chatwork.com/v2/rooms/$CHATWORK_ROOM/messages"
      fi

Do trong file config.yml không cho phép chúng ta viết trực tiếp lệnh curl như trên nên ta sẽ bọc nó vào với điều kiện if của cú pháp bash. Như bạn thấy ở trên ta dùng $CHATWORK_TOKEN$CHATWORK_ROOM để đặt phần token vào header và room id vào phần lời gọi API. Với lệnh trên sau khi chạy "thành công" các lệnh trước đó là:

# run test
- run: php artisan key:generate
- run: framgia-ci test-connect 127.0.0.1 3306 60
- run: php artisan migrate
- run: framgia-ci run --local

Thì nó sẽ gửi lại message trên về room mà chúng ta đặt như trên. Bây giờ chúng ta hãy lưu lại các thay đổi sau đó push lại lên github để kiểm tra kết quả của:

Lưu ý: lệnh gửi notification này cũng được thực hiện trong container với image mà chúng ta đã khai báo vì thể để dùng được curl thì bạn phải chắc rằng trong image đó đã cài curl.

Như bạn có thể thấy, ở đây sau khi chạy xong lệnh framgia-ci run --local thì sẽ đến lệnh gửi thông báo lên chatwork room mà chúng ta đã đăng kí và nếu gửi thành công bạn sẽ thấy mục message_id như trong hình. Còn đây là thông báo mà ta nhận được trên chatwork:

Phần nội dung tin nhắn, bạn có thể điền bất cứ nội dung gì giống như chatwork tuy nhiên ở đây để lấy được các thông tin mà mình đã đề cập đến ban đầu thì chúng ta cần sử dụng thêm các biến môi trường mặc định của bản build mà CircleCI hỗ trợ. Danh sách cụ thể các biến bạn có thể xem ở đây. Ứng với mỗi yêu cầu ban đầu mà ta đặt ra, đây là các biến mà ta sẽ lấy:

  • Tên repository: $CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
  • Người tạo bản build: $CIRCLE_USERNAME
  • Tên branch: $CIRCLE_BRANCH
  • Link dẫn tới commit/pullrequest đó trên github: https://github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/commit/$CIRCLE_SHA1
  • Link dẫn tới bản build đó trên CircleCI: https://circleci.com/gh/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/$CIRCLE_BUILD_NUM

Với những thông tin trên ta có thể cập nhật lại phần message cho chatwork notification trong file config.yml như sau:

# Send notification to chatwork
      - run:
          name: Sending notification
          command: |
            if [[ true ]];  then
              curl -X POST -H "X-ChatWorkToken: $CHATWORK_TOKEN" -d \
                "body=
                - Repository name: $CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
                - Author: $CIRCLE_USERNAME
                - Branch: $CIRCLE_BRANCH
                - Commit/pull request link: https://github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/commit/$CIRCLE_SHA1
                - Build link: https://circleci.com/gh/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/$CIRCLE_BUILD_NUM
                " \
                "https://api.chatwork.com/v2/rooms/$CHATWORK_ROOM/messages"
            fi

Sau đó lưu lại và thử push lại lên và đây là kết quả ta thu được ở chatwork:

Tuy nhiên ở đây vẫn còn 2 vấn đề mà ta cần giải quyết như sau:

  • Notification trả về không hề có thông tin về bản build đó thành công hay thất bại vì CircleCI không cung cấp cho chúng ta biến này
  • Notification chỉ trả về khi các lệnh trước đó thành công, nếu một trong cách lệnh trước đó thất bại thì sẽ không có thông báo trả về

Để giải quyết 2 vấn đề nêu trên, chúng ta sử dụng một lệnh hỗ trợ mà CircleCI cung cấp sẵn cho chúng ta đó là when. Lệnh hỗ trợ này sẽ đi kèm với lệnh chính của chúng ta dưới dạng sau:

- run:
    name: Do something
    when: <option>
    command: |
      echo "Hello"

Lệnh echo "Hello" sẽ thực hiện khi đáp ứng <option> mà chúng ta khai báo trong when. Cụ thể, CircleCI cung cấp 3 option đó là on_success, on_failalways tương ứng với khi bản build thành công, khi bản build bị lỗi hoặc luôn luôn. Mặc định khi bạn không khai báo gì nó sẽ có giá trị là on_success. Như vậy áp dụng lệnh when vào phần notification của chúng ta sẽ được kết quả như sau:

# Send notification to chatwork
      - run:
          name: Sending notification
          when: on_success
          command: |
            if [[ true ]];  then
              curl -X POST -H "X-ChatWorkToken: $CHATWORK_TOKEN" -d \
                "body=
                Your have a success build !
                - Repository name: $CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
                - Author: $CIRCLE_USERNAME
                - Branch: $CIRCLE_BRANCH
                - Commit/pull request link: https://github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/commit/$CIRCLE_SHA1
                - Build link: https://circleci.com/gh/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/$CIRCLE_BUILD_NUM
                " \
                "https://api.chatwork.com/v2/rooms/$CHATWORK_ROOM/messages"
            fi
      - run:
          name: Sending notification
          when: on_fail
          command: |
            if [[ true ]];  then
              curl -X POST -H "X-ChatWorkToken: $CHATWORK_TOKEN" -d \
                "body=
                Your have a fail build !
                - Repository name: $CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
                - Author: $CIRCLE_USERNAME
                - Branch: $CIRCLE_BRANCH
                - Commit/pull request link: https://github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/commit/$CIRCLE_SHA1
                - Build link: https://circleci.com/gh/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/$CIRCLE_BUILD_NUM
                " \
                "https://api.chatwork.com/v2/rooms/$CHATWORK_ROOM/messages"
            fi

Như vậy với mỗi trường hợp bản build thành công hay thất bại ta cũng sẽ gửi được notification về chatwork với nội dung chỉ khác nhau ở phần You have a success/fail build !.

3. Auto deploy với Heroku


Mặc định CircleCI cũng hỗ trợ cho chúng ta sẵn việc auto deploy project lên Heroku. Đầu tiên bạn cần tạo 1 project trên Heroku. Sau đó bạn vào phần account setting rồi lăn xuống phần API key, bấm Reveal và copy lại nó:

Tiếp theo bạn vào lại phần Enviroment Variables của project trên CircleCI và thêm vào dưới dạng với tên là HEROKU_API_KEY. Tiếp đến bạn thêm một biến nữa có tên là HEROKU_APP_NAME có giá trị là tên mà bạn đặt cho project của mình trên Heroku. Cuối cùng trong file config.yml bạn thêm một mục mới, tách biệt với mục ban đầu như sau:

version: 2
jobs:
  build:
    docker:
      - image: 7896/civ3-workspace:2.0
      - image: mysql:5.7
        environment:
          MYSQL_HOST: 127.0.0.1
          MYSQL_DATABASE: homestead
          MYSQL_USER: homestead
          MYSQL_PASSWORD: secret
          MYSQL_ROOT_PASSWORD: root
    steps:
      - checkout
      - run: cp .env.example .env

      # composer cache
      - restore_cache:
          keys:
            - vendor-v2-{{ checksum "composer.lock" }}
      - run:  composer install
      - save_cache:
          key: vendor-v2-{{ checksum "composer.lock" }}
          paths:
            - vendor

      # run test
      - run: php artisan key:generate
      - run: framgia-ci test-connect 127.0.0.1 3306 60
      - run: php artisan migrate
      - run: framgia-ci run --local

      # Send notification to chatwork
      - run:
          name: Sending notification
          when: on_success
          command: |
            if [[ true ]];  then
              curl -X POST -H "X-ChatWorkToken: $CHATWORK_TOKEN" -d \
                "body=
                Your have a success build !
                - Repository name: $CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
                - Author: $CIRCLE_USERNAME
                - Branch: $CIRCLE_BRANCH
                - Commit/pull request link: https://github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/commit/$CIRCLE_SHA1
                - Build link: https://circleci.com/gh/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/$CIRCLE_BUILD_NUM
                " \
                "https://api.chatwork.com/v2/rooms/$CHATWORK_ROOM/messages"
            fi
      - run:
          name: Sending notification
          when: on_fail
          command: |
            if [[ true ]];  then
              curl -X POST -H "X-ChatWorkToken: $CHATWORK_TOKEN" -d \
                "body=
                Your have a fail build !
                - Repository name: $CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
                - Author: $CIRCLE_USERNAME
                - Branch: $CIRCLE_BRANCH
                - Commit/pull request link: https://github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/commit/$CIRCLE_SHA1
                - Build link: https://circleci.com/gh/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/$CIRCLE_BUILD_NUM
                " \
                "https://api.chatwork.com/v2/rooms/$CHATWORK_ROOM/messages"
            fi
            
  deploy-heroku:
    docker:
      - image: buildpack-deps:trusty
    steps:
      - checkout
      - run:
          name: Deploy to Heroku
          command: |
            git config --global push.default current
            git push https://heroku:[email protected]/$HEROKU_APP_NAME.git develop:master -f

Chú ý lúc nào trong file config.yml của bạn sẽ có 2 mục chính đó là builddeploy-heroku. Bạn có thể hiểu đây là 2 công việc hay 2 job khác nhau. Build là job dùng để chạy test cho project của chúng ta còn deploy-heroku là job chịu trách nhiệm cho việc deploy lên heroku. Ở trên là cấu trúc mẫu mà CircleCI cung cấp cho chúng ta trong việc deploy với Heroku tuy nhiên ở phần cuối mình có thêm đoạn develop:master -f cho phép chúng ta có thể đẩy code từ nhánh develop vào nhánh master trên Heroki bởi mặc định Heroku sẽ deploy ở nhánh master mà thôi. Sở dĩ mình làm vậy bởi có thể bạn sẽ sử dụng Heroku làm server dev còn VPS làm server product nên việc merge code hay push code ở develop sẽ được deploy lên Heroku còn master sẽ lên VPS.

Cuối cùng để chạy được cả 2 job trên ta cần tạo ra 1 workflow hay 1 luồng chạy với cú pháp như sau:

version: 2
jobs:
  build:
    ...
    
 deploy-heroku:
   ...

workflows:
  version: 2
  build-n-deploy:
    jobs:
      - build
      - deploy-heroku:
          requires:
            - build
          filters:
            branches:
              only:
                - develop

Workflow mà chúng ta mới thêm ở trên rất đơn giản, nội dung chính sẽ bắt đầu sau chữ jobs. Cụ thể ta sẽ chạy job build mà chúng ta khai báo ở trên sau đó mới chạy job deploy-heroku. Tuy nhiên khác với job build sẽ chạy mặc định thì với job deploy-heroku ta có một số điều kiện sau đó là:

  • requires: Chỉ chạy sau khi job build thành công nghĩa là bản build của chúng ta success và không có lỗi gì. Bạn có thể thêm nhiều job khác và có thể thêm vào phần requires như mình làm ở trên để đảm bảo tất cả các job trong danh sách đó đều phải thành công trước khi chạy job này
  • filters/branches: Chỉ áp dụng với branch develop hay chính xác là chỉ chạy job này khi thao tác push/merge code diễn ra trên nhánh develop.

Sau khi sửa lại xong bạn có thể thử đẩy lại code lên. Lúc này trên CircleCI bạn sẽ thấy nó xuất hiện trong phần workflow:

Trên đây là 2 lần chạy workflow một lần thất bại và một lần thành công ở job build. Cả 2 trường hợp ta đều chạy với branch develop trên github. Cụ thể với trường hợp thất bại ta bấm vào workflow đó sẽ thu được kết quả như sau:

Do phần chạy job build thất bại nên job deploy-heroku sẽ không được chạy. Còn đối với lần chạy job build success kết quả sẽ như sau:

Như bạn có thể thấy ở đây khi job build chạy success sẽ tiếp tục chạy đến jon deploy-heroku. Bạn cũng có thể bấm vào job mình muốn để xem được log như các bản build thông thường:

4. Hạn chế khi sử dụng CircleCI


Như mình đã nói ở bài viết trước, mặc dù CircleCI cung cấp cho chúng ta rất nhiều lợi ích tuy nhiên nếu bạn sử dụng plan-free thì có một số lưu ý:

  • Đối với project dạng open source/ public repo thì bạn có thể sử dụng khá thoải mái và có thể chạy tới 4 bản build song song cùng với các tiện ích khác như lưu trữ file log, xem thống kê project
  • Đối với project dạng private thì có khá nhiều hạn chế như bạn không thể xem thống kê project và số bản build song song bị giới hạn chỉ còn 1 mà thôi. Nghĩa là khi có 2 người cùng gửi pull request thì CircleCI sẽ chạy từng bản build một và bản build đến sau phải nằm trong hàng chời bản build thứ nhất chạy xong mới tiếp tục thực hiện. Điều này sẽ trở nên khá phiền phức nếu project của bạn mất từ 10-15p cho một bản build và số lượng dev nhiều dẫn đến mọi bạn build sẽ phải chờ nhau làm giảm hiệu quả công việc. Bạn cũng có thể giải quyết vấn đề trên bằng cách up lên plan trả phí. Tuy nhiên chi phí trên CircleCI khá cao với khoảng 150$ /tháng cho 4 bản build chạy song song như plan free.

5. Kết bài


Qua hai bài viết của mình về CircleCI, mong rằng có thể giúp ích cho bạn trong quá trình làm việc thường ngày của mình. Nếu bạn có bất cứ thắc mắc gì hãy comment ở phía dưới và nhớ upvote cho bài viết của mình 😄. Cảm ơn bạn đã đọc !!!