• जटिल CI को GitHub Actions पर वापस लाने की प्रक्रिया में merge queue, कई runner, Rust build, Docker image और integration test आपस में जुड़ गए, जिससे configuration से ज़्यादा debugging की लागत सामने आई
  • main में जाने वाले हर change को सभी test पास करने चाहिए, और format, unused dependency, lint जैसी छोटी गलतियां अपने-आप ठीक होनी चाहिए; साथ ही CI artifact वही होने चाहिए जो release artifact हैं
  • merge queue से पहले और बाद दोनों validation को enforce करने के लिए दोनों चरणों के job names को समान रखना पड़ा; ऐसा न करने पर check अटक सकते हैं या failed changes merge हो सकते हैं
  • GITHUB_TOKEN, workflow permissions, custom token, fork और self-hosted runner exceptions मिलकर security model को समझना मुश्किल बनाते हैं और गलती की गुंजाइश बढ़ाते हैं
  • Docker container execution, YAML workflow और limited local testing के कारण development speed धीमी हुई, लेकिन नए CI script ने merge time को काफी घटा दिया

GitHub Actions पर लौटा जटिल CI

  • पिछले 2 हफ्तों में CI scripts को फिर से GitHub Actions में लिखा गया
  • CI configuration का यह तीसरा overhaul है
    • शुरुआत GitHub Actions से हुई थी
    • इसके बाद Earthly पर move किया गया
    • Earthly बंद हो गया, इसलिए वापस GitHub Actions पर लौटना पड़ा
  • मौजूदा CI merge queue, कई runner, Rust build, Docker image और भारी integration tests को साथ संभालता है
    • runner के तौर पर self-hosted, blacksmith.sh और GitHub-hosted सभी का उपयोग होता है
    • एक PR merge करते समय कई parallel runner पर करीब 1 घंटे का CI time खर्च होता है

CI को किन शर्तों को पूरा करना है

  • main में जाने वाले हर change को सभी tests पास करने चाहिए
  • format, unused dependency और lint issues जैसी छोटी गलतियां failure में खत्म होने के बजाय अपने-आप ठीक होनी चाहिए
  • CI में test किए गए artifacts वास्तविक release artifacts जैसे ही होने चाहिए
  • developer experience के लिए CI जल्दी खत्म होना चाहिए
  • GitHub Actions इन शर्तों को technically support करता है, लेकिन setup process में छिपे traps, inconsistent behavior और मुश्किल debugging साथ आती है

Merge queue और status checks

  • साफ main branch बनाए रखने की कुंजी GitHub की merge queue है
    • merge queue CI चलने से पहले PR को main के ऊपर rebase करती है
  • जरूरी CI execution दो चरणों में बंटता है
    • queue में जाने से पहले CI चलाकर छोटी समस्याएं अपने-आप ठीक की जाती हैं
    • queue के अंदर फिर से CI चलाकर final merge state verify की जाती है
  • GitHub Actions में दोनों executions को required बनाने के लिए दोनों चरणों के job names समान रखने पड़े
    • GitHub दोनों executions को एक ही check मानता है, इसलिए merge तभी संभव होता है जब दोनों सफल हों
    • यह तरीका कई घंटे debugging करने के बाद Stack Overflow answer से मिला
  • दूसरे तरीकों में status check queue में entry से पहले pending रहकर job शुरू नहीं होता, या merge queue के अंदर fail होना चाहिए ऐसा job fail होने पर भी merge हो सकता है

समझने में कठिन GitHub Actions security model

  • हाल में एक लोकप्रिय GitHub Action के compromise होने की घटना के बाद “dependencies को hash पर pin करो” जैसी प्रतिक्रिया आई, लेकिन comments में प्रतिक्रिया थी कि लगभग कोई ऐसा नहीं करता
  • GitHub Actions में default token GITHUB_TOKEN होता है
    • यह token default permissions के साथ initialize होता है
    • default permissions repository settings के Actions → General → Workflow Permissions में set किए जा सकते हैं
  • अगर GITHUB_TOKEN की default permissions restrictive हों, तो जरूरी action और command execution के लिए permissions बढ़ानी पड़ती हैं; अगर default permissions permissive हों, तो workflow file में कुछ permissions हटाई जा सकती हैं
  • बेहतर default यह होगा कि कोई permission नहीं से शुरुआत हो और user सिर्फ जरूरी permissions जोड़े
  • permission types बहुत ज्यादा हैं, और GitHub expert न होने पर यह समझना मुश्किल है कि कौन-सी permission किस चीज की रक्षा करती है

Token और permission exceptions

  • softprops/action-gh-release से GitHub release automatically बनाने के लिए custom token CI_RELEASE का उपयोग किया जाता है
- name: Release on GitHub
  if: env.version_exists == 'false'
  uses: softprops/action-gh-release@v2
  with:
    tag_name: v${{ env.CURRENT_VERSION }}
    generate_release_notes: true
    make_latest: true
    token: ${{ secrets.CI_RELEASE }}
  • default token से भी release खुद पूरा हो जाता है, लेकिन release के बाद workflow trigger नहीं होता
    • कोई अलग indication नहीं होता, इसलिए वजह जानने के लिए इसी समस्या से जूझ चुके व्यक्ति की छोड़ी गई issue ढूंढनी पड़ती है
  • workflow YAML के अंदर permissions बढ़ाई भी जा सकती हैं
    • जिस code को protect करना है, उसी के अंदर permissions बढ़ाने की संरचना अजीब लगती है
  • GitHub docs के अनुसार permissions key से fork repository की read permissions add/remove की जा सकती हैं, लेकिन आम तौर पर write permissions नहीं दी जा सकतीं
    • exception तब है जब admin GitHub Actions settings में Send write tokens to workflows from pull requests option चुनता है
  • exceptions और traps बहुत हैं, इसलिए GitHub Actions security model शक्तिशाली होने के साथ-साथ attack surface और गलती की संभावना भी बढ़ाता है

Self-hosted runner की अनिश्चितता

  • GitHub docs public repositories में self-hosted runner उपयोग करने की सलाह नहीं देते
    • क्योंकि public repository का fork PR के जरिए self-hosted runner machine पर खतरनाक code चला सकता है
  • GitHub में ऐसा self-hosted runner setting भी है जिसमें external contributor के PR execution को approve करना जरूरी होता है
  • इस setting और self-hosted runner को साथ इस्तेमाल करने पर यह सुरक्षित है या नहीं, इसका स्पष्ट जवाब docs में नहीं है और internet पर भी consensus नहीं है
  • complexity ज्यादा होने से यह स्थिति ऐसी रहती है जिसमें 100% भरोसा करना मुश्किल है

Docker और GitHub Actions का टकराव

  • GitHub Actions jobs को container के अंदर चला सकता है
    • इसका फायदा यह है कि dependencies हर बार install करने के बजाय dev container में पहले से package की जा सकती हैं
  • वास्तविक उपयोग में file permissions की समस्या बार-बार आती है
    • container एक user के रूप में files build करता है, लेकिन GitHub runner किसी अलग uid/gid से चल सकता है
    • नतीजतन container के अंदर की files, GitHub workspace या temporary host directories तक access नहीं मिल सकता
  • $HOME directory भी mismatch हो सकती है
    • dev container tools को /home/ubuntu में install कर सकता है
    • GitHub Actions के अंदर $HOME /github/home हो सकता है
    • $HOME के नीचे files पर निर्भर tools जरूरी files नहीं ढूंढ पाते
  • host system से interact करने वाले actions भी टूट सकते हैं
    • GitHub cache 10GB तक सीमित है, इसलिए blacksmith के sticky disk action से caching के लिए NVMe drive mount की गई
    • यह container के अंदर काम नहीं कर रहा था, और blacksmith.sh के fix के बाद हल हुआ
  • container field की अपनी limitations भी हैं
    • entrypoint override नहीं किया जा सकता
    • कुछ steps को container के अंदर और बाकी को बाहर चलाने का तरीका भी संभव नहीं है

YAML workflow development और debugging

  • GitHub Actions logic YAML में लिखा जाता है, और जैसे-जैसे यह जटिल होता है, गलतियां करना आसान हो जाता है
  • RustRover के GitHub YAML linter checks मददगार रहे, लेकिन बेहतर static checks की जरूरत है
  • local में वास्तविक CI behavior को पर्याप्त रूप से test करना मुश्किल है
    • act एक जाना-पहचाना tool है, लेकिन CI में किए जाने वाले कामों का सिर्फ छोटा हिस्सा support करता है
  • सबसे अच्छा debugging तरीका था उसी repository की एक और copy बनाना और जब तक CI expected तरीके से काम न करे, git commit -a -m "wip" && git push test-ci branch दोहराते रहना

Workflow separation और artifact reuse

  • हर बार पूरी CI pipeline न चलानी पड़े, इसलिए individual workflows को छोटा रखा गया
  • हर workflow के अंत में artifacts upload किए जाते हैं, और बाद का workflow उन्हें download करके शुरुआत से फिर build करने से बचता है
  • पिछली run के artifacts download करके workflow को isolated तरीके से test किया जा सकता है
    • हालांकि पिछली run से download करते समय download-artifact action को token देना पड़ता है
    • यह token default token भी हो सकता है, लेकिन अब भी इसे explicitly देना क्यों जरूरी है, यह unresolved सवाल है
  • main workflow file दूसरी YAML files को call करने वाली chain बन जाती है
jobs:
  invoke-build-rust:
    name: Build Rust
    uses: ./.github/workflows/build-rust.yml
  invoke-build-java:
    name: Build Java
    uses: ./.github/workflows/build-java.yml
  invoke-tests-unit:
    name: Unit Tests
    needs: [invoke-build-rust, invoke-build-java]
    uses: ./.github/workflows/test-unit.yml
  invoke-tests-adapter:
    name: Adapter Tests
    needs: [invoke-build-rust]
    uses: ./.github/workflows/test-adapters.yml
    secrets: inherit
  invoke-build-docker:
    name: Build Docker
    needs: [invoke-build-rust, invoke-build-java]
    uses: ./.github/workflows/build-docker.yml
  invoke-tests-integration:
    name: Integration Tests
    needs: [invoke-build-docker]
    uses: ./.github/workflows/test-integration.yml
  invoke-tests-java:
    name: Java Tests
    needs: [invoke-build-java]
    uses: ./.github/workflows/test-java.yml
  • कुछ jobs में secrets: inherit जरूरी है
    • जब कोई workflow दूसरे workflow को call करता है, तो secrets default रूप से share नहीं होते
    • यही उस समस्या की वजह थी जिसमें पूरी CI pipeline में failure होता था, लेकिन individual step execution में काम करता था

तेज merge, फिर भी महंगी debugging

  • नए CI scripts ने merge time को काफी कम किया और परिणाम से संतोष है
  • हालांकि उस state तक पहुंचने में बहुत ज्यादा समय लगा, और समस्या आने पर debugging आसान होनी चाहिए

अभी कोई टिप्पणी नहीं है.

अभी कोई टिप्पणी नहीं है.