Home Tools GitHub Actions คืออะไร

GitHub Actions คืออะไร

by khomkrit

นอกจากเราควรต้องเอาใจใส่ต่อการใช้ Git ที่เหมาะสมกับทีมแล้ว อีกสิ่งหนึ่งที่ขาดไม่ได้ก็คือการเอาใจใส่กับฟีเจอร์ต่างๆ ที่ git repository provider แต่ละเจ้านั้นมีให้เราใช้ ซึ่งฟีเจอร์หนึ่งที่ควรเรียนรู้เสียอย่างหลีกเลี่ยงไม่ได้ก็คืองาน automation และ GitHub เองก็มีสิ่งที่เรียกว่า GitHub Actions มาให้เราใช้งานเช่นกัน

GitHub Actions ทำให้เราสามารถทำอะไรบางอย่างได้แบบอัตโนมัติ หลังจากเกิดเหตุการณ์ (event) ที่เราสนใจใน Repository ของเรา เช่น เมื่อมีคนเปิด pull request ก็จะให้รัน test ให้เราก่อนว่า pull request นั้นทำอะไรของเราพังไปบ้าง หรือถ้ามีคน push เข้ามาที่ branch main ก็ให้ deploy ไปที่ host ที่เราต้องการโดยอัตโนมัติทันที เป็นต้น

สิ่งเหล่านี้ที่กล่าวมา เราเรียกมันว่า workflow แต่ละ workflow ประกอบไปด้วยงาน (job) ต่างๆ ซึ่งแต่ละงานอธิบายว่ามีขั้นตอน (step) อะไรที่ต้องทำ (action) บ้าง

GitHub Actions components
Introduction to GitHub Actions – docs.github.com

แปลว่าถ้าเราต้องการให้ทำอะไรสักอย่างหลังจากมีเหตุการณ์ที่เราสนใจเกิดขึ้น เราต้องสร้าง workflow

สร้าง Workflow

workflow จะทำงาน (job) ต่างๆ ตาม event ที่เรากำหนดไว้ ดังนั้นเมื่อใดที่มี event ที่ว่า เมื่อนั้น workflow ทำงาน

เราจะสร้าง workflow ไว้ใน .github/workflows/ โดยเขียนไว้ในไฟล์ .yml

ยกตัวอย่าง ถ้าเราต้องการสร้าง workflow ที่จะทำงานก็ต่อเมื่อมีใครสักคน push code ขึ้นมาที่ branch master ก็จะสร้างไฟล์ .yml ได้ดังนี้

name: My Workflow
on: 
  push:
    branches:
      - master

หรือถ้าจะให้ทำงานทุกครั้งที่มีการ push โดยไม่กำหนด branch ก็เขียนได้สั้นลงได้แบบนี้

name: My Workflow
on: [push]

สร้างงานให้ workflow

workflow ต้องทำงาน (job) และแต่ละงานก็ต้องบอกด้วยว่า มีขั้นตอน (step) ว่าต้องทำอะไรบ้าง ยกตัวอย่างเช่น ถ้าเราต้องการสร้าง งาน (job) ชื่อ build และงานนี้จะรันบน Ubuntu เราสามารถเขียน ได้ดังนี้

name: My Workflow
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest

สร้างขั้นตอนการทำงาน

หลังจากเรานิยามไปแล้วว่าเราจะทำงานชื่อ build เราก็ต้องบอกด้วยว่างานนี้มีขั้นตอนการทำงานอย่างไรบ้าง โดยการใช้คำสั่ง steps ในการกำหนดขั้นตอนการทำงานดังนี้

name: My workflow
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
      - run: npm install -g bats
      - run: bats -v

จากโค้ดจะเห็นว่ามีขั้นตอนการทำงานทั้ง 4 ขั้นตอน แต่ละขั้นตอนจะทำงานตามลำดับก่อนหลัง โดยที่ 2 ขั้นตอนแรกใช้คำสั่ง uses และสองขั้นตอนหลังใช้คำสั่ง run

การใช้คำสั่ง uses มีความหมายว่าให้รัน action ตามที่ระบุไว้ กรณีนี้คือ ขั้นตอนแรกให้รัน action จาก actions/checkout@v2 พอรันเสร็จแล้ว ขั้นตอนที่สองก็ให้รัน action จาก actions/setup-node@v1 ต่อทันที

ขั้นตอนที่ 3 และ 4 เป็นการบอกให้ Runner รันคำสั่ง npm install -g bats และ bats -v ตามลำดับ

Runner คือคนที่กำลังรัน Job นั้นๆ อยู่ ซึ่งนั่นแปลว่า ถ้าใน workflow ใดๆ มี Job มากกว่า 1 Job ก็จะมี Runner มากกว่า 1 Runner แต่ละ Runner ก็รับผิดชอบ Job ของใครของมันแยกกัน

แต่ละ Job ใน workflow ใดๆ จะทำงาน parallel กันโดย default แต่ถ้าเราต้องการให้ job หนึ่งจะทำงานได้ก็ต่อเมื่ออีก job หนึ่งทำงานเสร็จก่อน เราสามารถใช้คำสั่ง needs ได้ ยกตัวอย่างเช่น เราต้องการให้ job setup เสร็จก่อน แล้ว job build ถึงจะทำงานต่อได้ ก็เขียนได้ดังนี้

jobs:
  setup:
    runs-on: ubuntu-latest
    steps:
      - run: ./setup_server.sh
  build:
    needs: setup
    steps:
      - run: ./build_server.sh

Marketplace

มาถึงตรงนี้ เราจะพบว่า ในแต่ละขั้นตอน (step) นั้น เราสามารถสั่งให้รัน action หรือรัน command ก็ได้ ซึ่งการรัน command นั้นก็คือ command ทั่วไปที่อยู่ใน Virtual Machine กรณีที่ยกตัวอย่างมาก็คือ Ubuntu

ส่วน action นั้นเราจะรู้ได้อย่างไรว่ามี action อะไรไว้ให้เราใช้แล้วบ้าง?

เราสามารถไปหา action ต่างๆ ที่มีคนทำไว้ให้เราแล้วได้ที่ GitHub Marketplace ยกตัวอย่างเช่นหากเราต้องการ อัพโหลดโปรเจ็คของเราไปวางไว้บน server ที่เราต้องการ พอลองหาดู เราก็พบว่า น่าจะใช้ action ชื่อ ssh deploy ตัวนี้ก็ได้

แต่ละ action สามารถรับ input หรือกำหนดค่าต่างๆ ก่อนเริ่มทำงานได้ และ action ssh deploy ตัวนี้ก็เช่นเดียวกัน เมื่อเราไปดูตรงส่วนของวิธีใช้งาน เราก็จะพบว่านอกจากเราต้องใช้คำสั่ง uses เพื่อบอกว่าให้รัน action ssh deploy ตัวนี้แล้ว เรายังต้องกำหนดค่าให้กับ env อีก ดังนี้

  - name: Deploy to Staging server
    uses: easingthemes/ssh-deploy@v2.1.5
    env:
      SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }}
      ARGS: "-rltgoDzvO"
      SOURCE: "dist/"
      REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
      REMOTE_USER: ${{ secrets.REMOTE_USER }}
      TARGET: ${{ secrets.REMOTE_TARGET }}

Secrets

จากโค้ดที่ยกตัวอย่างไปเราจะเห็นการใช้ตัวแปร secrets ใน workflow ทั้งที่จริงๆ แล้วตรงนี้เราสามารถกำหนดเป็นค่าคงที่ลงไปได้เลย แต่บางค่าที่ค่อนข้าง sensitive เราจะเข้าไปกำหนดไว้ใน Settings → Secrets ของ GitHub project เรา หลังจากนั้นเราจะสามารถเรียกใช้ค่าเหล่านี้ได้ผ่านตัวแปร secrets

ค่าที่เรากำหนดไว้ใน Secrets นั้นเมื่อกำหนดแล้วจะไม่สามารถเปิดดูได้ว่ากำหนดค่าอะไรไป ทำได้แค่ลบ หรืออัพเดทเป็นค่าใหม่เท่านั้น

github secrets

สร้าง Action เอง

หาก action ต่างๆ ใน GitHub Marketplace นั้นยังไม่ถูกใจเรา เราก็สามารถสร้าง action ขึ้นมาเองได้ โดยที่ action จะแบ่งออกเป็น 3 แบบได้แก่ action ที่รันใน Docker container, รันด้วย JavaScript และ action ที่รัน command ตามปกติ

แต่ละ action เราจำเป็นต้องมีไฟล์ action.yml ทำหน้าที่เป็น metadata file ที่ใช้กำหนด input, output และ environment variable ของ action

ไม่ว่า action ที่เราสร้างจะเป็น Docker container action หรือ JavaScript action หรือ command line action ก็จะมีเนื้อหาในไฟล์ action.yml เกี่ยวกับวิธีกำหนด inputs, outputs แบบเดียวกัน แต่จุดที่ต่างกันหลักๆ ก็คือตอนสั่งรันด้วยคำสั่ง runs using:

ตัวอย่างตรงคำสั่ง runs ถ้าเป็น Docker action

runs:
  using: 'docker'
  image: 'Dockerfile'
  args:
    - ${{ inputs.who-to-greet }}

ตัวอย่างตรงคำสั่ง runs ถ้าเป็น JavaScript action

runs:
  using: 'node12'
  main: 'index.js'

ตัวอย่างตรงคำสั่ง runs ถ้าเป็น command line ปกติ

runs:
  using: "composite"
  steps: 
    - run: echo Hello ${{ inputs.who-to-greet }}.
      shell: bash
    - run: ${{ github.action_path }}/goodbye.sh
      shell: bash

ต่อไปนี้คือตัวอย่าง เต็มๆ ของ metadata file ของ JavaScript Action ที่เราประกาศส่วน inputs, outputs และ runs

name: 'Hello World'
description: 'Greet someone and record the time'
inputs:
  who-to-greet:  # id of input
    description: 'Who to greet'
    required: true
    default: 'World'
outputs:
  time: # id of output
    description: 'The time we greeted you'
runs:
  using: 'node12'
  main: 'index.js'

who-to-greet คือชื่อ input ที่รับเข้ามา ซึ่งเราต้องใช้ตอนเรียกใช้ action นี้ใน workflow และ time คือชื่อ output ที่เราสามารถอ้างดึงได้ใน workflow และ action นี้รันไฟล์ index.js

JavaScript action จะมี package มาช่วยให้เราสร้าง action ได้ง่ายๆ 2 ตัวคือ @actions/core และ @actions/github ดังนั้นเวลาเราเขียน action เราต้อง import มันเข้ามาใช้งานด้วย ดังนี้

const core = require('@actions/core');
const github = require('@actions/github');

try {
  // `who-to-greet` input defined in action metadata file
  const nameToGreet = core.getInput('who-to-greet');
  console.log(`Hello ${nameToGreet}!`);
  const time = (new Date()).toTimeString();
  core.setOutput("time", time);
  // Get the JSON webhook payload for the event that triggered the workflow
  const payload = JSON.stringify(github.context.payload, undefined, 2)
  console.log(`The event payload: ${payload}`);
} catch (error) {
  core.setFailed(error.message);
}

เราจะเห็นว่า action นี้อ่าน input ชื่อ who-to-greet เข้ามา และบันทึกผลลัพธ์ไว้ในตัวแปร time สอดคล้องกับในไฟล์ action.yaml ที่ได้นิยาม input/output เอาไว้ตอนแรก

ตอนใช้งาน เราก็สามารถกำหนด input และอ้างดึง output ได้เลย โดยที่เราจำเป็นต้องกำหนด id ใก้กับ action ไว้ด้วย เพื่อให้เราสามารถอ้างถึง output ได้

เช่น เรากำหนด id เป็น hello เวลาเราจะอ้างถึง output ของ action ที่มี id นี้เราก็สามารถเขียนได้ว่า

steps.hello.outputs

ตัวอย่างการใช้งาน action และการอ้างถึง output ของ action

    steps:
      - name: Hello world action step
        uses: ./ # Uses an action in the root directory
        id: hello
        with:
          who-to-greet: 'Mona the Octocat'
      - name: Get the output time
        run: echo "The time was ${{ steps.hello.outputs.time }}"

ส่งค่าระหว่าง Job

หากใน workflow เดียวกันมี job มากกว่า 1 job เช่นมี job แรกชื่อ build และ job ที่ 2 คือ deploy ซึ่ง job deploy นี้จะต้องอัพโหลดผลที่ได้จาก job build เราจะใช้ Artifacts เก็บผลลัพธ์ที่ได้จาก job build

เราจะใช้ Artifacts เก็บผลที่ได้จาก job build โดยใช้ action ชื่อ actions/upload-artifact@v2 สำหรับเก็บผลลัพธ์ และ job deploy จะใช้ action/download-artifact@v2 สำหรับอ่านค่าออกมาใช้งาน

ตัวอย่างต่อไปนี้คือการสร้าง 2 job โดยที่ job แรกชื่อ build และได้ผลลัพธ์ออกมาเป็น directory ชื่อ dist จานั้นบันทึกเก็บไว้ใน Artifacts

และเมื่อ build เสร็จแล้ว job deploy จะอ่านไฟล์ dist ออกมาจาก Artifacts และอัพโหลดขึ้นไป deploy บน server

name: Auto Deploy to my server
on: 
  push:
    branches:
      - master

jobs:
  build:
    name: 'Build App'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - name: 'Install dependency'
        run: |
          npm install
          npm run build
      - name: 'Archive production artifacts'
        uses: actions/upload-artifact@v2
        with:
          name: 'dist'
          path: dist

  deploy:
    needs: build
    name: 'Deploy to production server'
    runs-on: ubuntu-latest
    steps:
      - name: 'Download artifact'
        uses: actions/download-artifact@v2
        with:
          name: dist
      - name: 'Deploy to Production Server'
        uses: easingthemes/ssh-deploy@v2.1.5
        env:
          SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }}
          ARGS: "-rltgoDzvO --delete"
          SOURCE: "."
          REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
          REMOTE_USER: ${{ secrets.REMOTE_USER }}
          TARGET: ${{ secrets.REMOTE_TARGET }}

สรุปเรื่อง GitHub Actions

ในบทความนี้ได้แนะนำการใช้งาน GitHub Actions โดยอธิบายให้เห็นภาพว่า เราสามารถสร้าง workflow ขึ้นมา โดยแต่ละ workflow จะ trigger ตาม event ที่กำหนดไว้

ใน workflow หากเรามีข้อมูลที่ sensitive และไม่อยาก push ขึ้นไปใน repository เราสามารถไปกำหนดไว้ได้ที่ Settings → Secret และเมื่อกำหนดแล้วเราจะไม่สามารถกลับไปเปิดดูค่าได้อีก นอกจากจะลบ หรืออัพเดทใหม่เท่านั้นที่ทำได้

แต่ละ workflow ประกอบไปด้วย Job อย่างน้อย 1 Job แต่ละ Job รัน parallel กัน แต่ก็สามารถให้รอกันได้ โดยใช้คำสั่ง needs

แต่ละ Job ประกอบไปด้วย steps ที่แต่ละ step ก็จะบอกว่าให้ทำอะไร รันคำสั่งอะไร หรือรัน action ไหน

แต่ละ action เราสามารถกำหนด input, environment variable ให้ได้ก่อนรัน และสามารถอ่าน output ของ action ออกมาใช้งานต่อได้

เราจะหา action ที่คนอื่นทำไว้แล้วได้ใน GitHub Marketplace มาใช้ได้เลย หรือจะสร้าง action ขึ้นมาเองก็ได้

การสร้าง action จะต้องมี metadata file ชื่อ action.yml เพื่อนิยามชนิดของ action, input และ output

ใช้ Artifacts ในการส่งผลลัพธ์ให้กันระหว่าง Job

ส่งท้าย

ในบทความนี้เป็นเพียงไกด์ไลน์สรุปย่อเกี่ยวกับการใช้งาน GitHub Actions เพื่อให้สามารถทำความเข้าใจและจับจุดได้เท่านั้น ยังมีอีกหลายเรื่องที่น่าสนใจที่ยังไม่ได้กล่าวถึง เช่น Security hardening และ Managing workflow runs เป็นต้น

You may also like

1 comment

Continuous Deployment กับ Flutter Web App และ Firebase - khomkrit ธันวาคม 8, 2020 - 11:26

[…] GitHub Action คืออะไร […]

Comments are closed.