Küçük Projelerin Çoğu İçin Yeten Bir GitLab CI/CD Pipeline'ı
Hiç CI'ı olmayan küçük bir projeyi devraldığımda, deployment genelde birinin SSH ile girip son commit'i çekmesi ve bir servisi elle restart etmesi anlamına geliyor. Çalışıyor, ta ki bir gün çalışmayana kadar: unutulan bir npm install, yanlış branch'ten bir deploy, ya da atlanmış bir environment variable yüzünden. Çözüm bir düzine stage'i olan karmaşık bir pipeline değil. Her seferinde aynı üç şeyi yapan kısa bir .gitlab-ci.yml: build, test, deploy.
Genel hali
stages:
- build
- test
- deploy
variables:
GIT_DEPTH: 1
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
build:
stage: build
image: node:20
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
test:
stage: test
image: node:20
script:
- npm ci
- npm run test -- --ci
GIT_DEPTH: 1, uzun geçmişi olan repo'larda checkout'ları hızlı tutuyor, ve node_modules'u branch'e göre cache'lemek, çoğu pipeline'ın full reinstall'dan kaçınması demek. Build stage'i, deploy stage'inin yeniden kullandığı bir artifact üretiyor, yani kod stage başına değil, pipeline başına bir kere build ediliyor.
SSH üzerinden deploy
Küçük projeler için bir Kubernetes manifest'i ya da container registry, projenin ihtiyacından fazla altyapı oluyor çoğu zaman. CI'dan tetiklenen düz rsync over SSH, şaşırtıcı sayıda gerçek deployment'ı kapsıyor:
deploy_staging:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client rsync
- eval $(ssh-agent -s)
- echo "$STAGING_SSH_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan -H "$STAGING_HOST" >> ~/.ssh/known_hosts
script:
- rsync -az --delete dist/ deploy@$STAGING_HOST:/var/www/staging/
environment:
name: staging
url: https://staging.example.com
rules:
- if: '$CI_COMMIT_BRANCH == "develop"'
SSH key'i repo'da asla yaşamıyor, maskelenmiş, protected bir CI/CD variable'ında tutuluyor. ssh-keyscan, known_hosts'u dolduruyor, böylece job bir host-key prompt'unda asılı kalmıyor, ve rsync --delete, yavaşça eski dosya biriktirmek yerine, remote dizini build çıktısıyla senkron tutuyor.
Production'a manuel bir kapı
Staging, develop'a her push'ta deploy oluyor. Production, main'den deploy oluyor, ama sadece biri butona bastığında:
deploy_production:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client rsync
- eval $(ssh-agent -s)
- echo "$PRODUCTION_SSH_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh && chmod 700 ~/.ssh
- ssh-keyscan -H "$PRODUCTION_HOST" >> ~/.ssh/known_hosts
script:
- rsync -az --delete dist/ deploy@$PRODUCTION_HOST:/var/www/production/
environment:
name: production
url: https://example.com
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
when: manual, job'ı otomatik tetiklenen bir şey olmaktan çıkarıp GitLab'ın pipeline görünümünde bir butona çeviriyor. GitLab'ın environment'larıyla birlikte, bu aynı zamanda önceki bir artifact'e tek tıkla rollback yapabileceğiniz bir deploy history veriyor, ve bu birden fazla kez kötü bir deploy'u panikten otuz saniyelik bir düzeltmeye çevirdi.
Neden genelde bu yeterli
Bu pipeline bir linter çalıştırmıyor, bir Docker image build etmiyor, Kubernetes'le konuşmuyor. Birçok küçük proje için bu sorun değil, proje büyüyüp ihtiyaç duyarsa bunlar sonra eklenebilir. Bu pipeline'ın yaptığı, manuel deploy'ların ters gittiği üç noktayı ortadan kaldırmak: biri dependency kurmayı unutuyor, biri test edilmemiş kod deploy ediyor, ya da biri yanlış branch'i deploy ediyor. Bu boyuttaki bir .gitlab-ci.yml üçünü de yakalıyor, ve projeye dokunacak bir sonraki kişinin bir dakikadan kısa sürede tamamını okuyabileceği kadar kısa.