Gönderi

GitHub Actions ile CI/CD Pipeline: Otomatik Test ve Deployment

GitHub Actions ile CI/CD pipeline oluşturma rehberi. Otomatik test, Docker build, multi-environment deployment ve güvenlik best practices.

GitHub Actions ile CI/CD Pipeline: Otomatik Test ve Deployment

Giriş

GitHub Actions, GitHub’ın yerleşik CI/CD (Continuous Integration/Continuous Deployment) platformudur. Kod değişikliklerini otomatik olarak test etme, build alma ve production’a deploy etme süreçlerini tamamen otomatikleştirir.

Bu rehberde, GitHub Actions ile sıfırdan production-ready bir CI/CD pipeline’ı nasıl oluşturacağınızı öğreneceksiniz. Test otomasyonu, Docker build, multi-environment deployment ve best practice’leri detaylıca ele alacağız.

GitHub Actions Nedir?

GitHub Actions, event-driven bir otomasyon platformudur. Repository’nizdeki belirli olaylar (push, pull request, release) tetiklendiğinde önceden tanımlanmış iş akışlarını (workflows) çalıştırır.

Temel Kavramlar

  • Workflow: Otomatik süreç tanımı (YAML dosyası)
  • Job: Workflow içindeki bağımsız görev grupları
  • Step: Job içindeki tek bir komut veya action
  • Action: Yeniden kullanılabilir otomasyon bileşeni
  • Runner: Workflow’ları çalıştıran sanal makine
  • Event: Workflow’ı tetikleyen olay (push, pull_request, schedule, vb.)

Workflow Dosya Yapısı

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# .github/workflows/ci.yml
name: CI Pipeline

# Tetikleyiciler
on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

# İş tanımları
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: npm test

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: Build application
        run: npm run build

İlk Workflow Oluşturma

Basit CI Workflow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# .github/workflows/hello-world.yml
name: Hello World

on:
  push:
    branches: [ main ]
  workflow_dispatch:  # Manuel tetikleme

jobs:
  greet:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Say hello
        run: echo "Hello, GitHub Actions!"
      
      - name: Show environment
        run: |
          echo "Repository: $"
          echo "Branch: $"
          echo "Actor: $"

workflow_dispatch event’i ile GitHub UI’dan manuel olarak workflow tetikleyebilirsiniz.

Python Projesi için CI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# .github/workflows/python-ci.yml
name: Python CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.9', '3.10', '3.11', '3.12']
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Set up Python $
        uses: actions/setup-python@v5
        with:
          python-version: $
      
      - name: Cache pip packages
        uses: actions/cache@v4
        with:
          path: ~/.cache/pip
          key: $-pip-$
          restore-keys: |
            $-pip-
      
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install pytest pytest-cov
      
      - name: Run tests
        run: |
          pytest --cov=. --cov-report=xml
      
      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          file: ./coverage.xml
          fail_ci_if_error: true

CI/CD Deployment Automation Flow CI/CD deployment otomasyonu akış diyagramı

Node.js/JavaScript CI/CD

Complete Node.js Workflow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# .github/workflows/node-ci-cd.yml
name: Node.js CI/CD

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

env:
  NODE_VERSION: '20.x'

jobs:
  # Lint ve format kontrolü
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: $
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run ESLint
        run: npm run lint
      
      - name: Check formatting
        run: npm run format:check
  
  # Test
  test:
    runs-on: ubuntu-latest
    needs: lint
    
    strategy:
      matrix:
        node-version: [18.x, 20.x, 22.x]
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js $
        uses: actions/setup-node@v4
        with:
          node-version: $
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run tests
        run: npm test -- --coverage
      
      - name: Upload coverage
        if: matrix.node-version == '20.x'
        uses: codecov/codecov-action@v4
        with:
          files: ./coverage/coverage-final.json
  
  # Build
  build:
    runs-on: ubuntu-latest
    needs: test
    if: github.ref == 'refs/heads/main'
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: $
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build application
        run: npm run build
      
      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
          retention-days: 7
  
  # Deploy
  deploy:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'
    
    steps:
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      
      - name: Deploy to production
        run: |
          echo "Deploying to production..."
          # Deploy komutları buraya

Docker Build ve Push

Docker Image Build

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# .github/workflows/docker-build.yml
name: Docker Build & Push

on:
  push:
    branches: [ main ]
    tags:
      - 'v*'
  pull_request:
    branches: [ main ]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: $

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: $
          username: $
          password: $
      
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: $/$
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha,prefix={{branch}}-
      
      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: $
          tags: $
          labels: $
          cache-from: type=gha
          cache-to: type=gha,mode=max

Multi-Stage Docker Build

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Dockerfile
# Build stage
FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

# Production stage
FROM node:20-alpine

WORKDIR /app

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./

USER node

EXPOSE 3000

CMD ["node", "dist/index.js"]

GitHub Actions Workflow YAML Configuration GitHub Actions workflow YAML konfigürasyon örneği

Matrix Testing

Cross-Platform Testing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# .github/workflows/matrix-test.yml
name: Matrix Testing

on: [push, pull_request]

jobs:
  test:
    runs-on: $
    
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        python-version: ['3.9', '3.10', '3.11', '3.12']
        exclude:
          # Windows'da Python 3.9 test etme
          - os: windows-latest
            python-version: '3.9'
        include:
          # Özel konfigürasyon ekle
          - os: ubuntu-latest
            python-version: '3.12'
            experimental: true
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python $
        uses: actions/setup-python@v5
        with:
          python-version: $
      
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
      
      - name: Run tests
        run: pytest
        continue-on-error: $

Database Matrix

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# .github/workflows/db-matrix.yml
name: Database Tests

on: [push, pull_request]

jobs:
  test-db:
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        database:
          - postgres:14
          - postgres:15
          - postgres:16
          - mysql:8.0
          - mysql:8.2
    
    services:
      db:
        image: $
        env:
          POSTGRES_PASSWORD: postgres
          MYSQL_ROOT_PASSWORD: mysql
        options: >-
          --health-cmd "pg_isready || mysqladmin ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
          - 3306:3306
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Run database tests
        run: |
          echo "Testing with $"
          # Test komutları

Secrets Yönetimi

API key’leri, şifreleri ve token’ları asla GitHub repository’nizde saklamayın! GitHub Secrets kullanarak güvenli şekilde yönetin.

Repository Secrets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# .github/workflows/deploy-with-secrets.yml
name: Deploy with Secrets

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: $
          aws-secret-access-key: $
          aws-region: us-east-1
      
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: $
          password: $
      
      - name: Deploy to server
        env:
          SSH_PRIVATE_KEY: $
          SERVER_HOST: $
        run: |
          mkdir -p ~/.ssh
          echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          ssh -o StrictHostKeyChecking=no user@$SERVER_HOST 'bash -s' < deploy.sh

Environment Secrets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# .github/workflows/multi-env-deploy.yml
name: Multi-Environment Deploy

on:
  push:
    branches: [ main, staging, develop ]

jobs:
  deploy-staging:
    if: github.ref == 'refs/heads/staging'
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.example.com
    
    steps:
      - name: Deploy to staging
        env:
          API_KEY: $
          DATABASE_URL: $
        run: |
          echo "Deploying to staging..."
  
  deploy-production:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://example.com
    
    steps:
      - name: Deploy to production
        env:
          API_KEY: $
          DATABASE_URL: $
        run: |
          echo "Deploying to production..."

CI/CD Pipeline Stages Diagram CI/CD pipeline aşamaları detaylı diyagram

Caching Stratejileri

Dependency Caching

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# .github/workflows/caching.yml
name: Caching Demo

on: [push, pull_request]

jobs:
  python-cache:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          cache: 'pip'  # Otomatik pip cache
      
      - name: Install dependencies
        run: pip install -r requirements.txt
  
  node-cache:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'  # Otomatik npm cache
      
      - run: npm ci
  
  custom-cache:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Cache build outputs
        uses: actions/cache@v4
        with:
          path: |
            ~/.cache
            build/
            dist/
          key: $-build-$
          restore-keys: |
            $-build-
            $-
      
      - name: Build project
        run: npm run build

Docker Layer Caching

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# .github/workflows/docker-cache.yml
name: Docker Layer Cache

on: push

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      - name: Build with cache
        uses: docker/build-push-action@v5
        with:
          context: .
          push: false
          cache-from: type=gha
          cache-to: type=gha,mode=max
          tags: myapp:latest

Artifacts (Yapılar)

Build Artifacts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# .github/workflows/artifacts.yml
name: Build Artifacts

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Build application
        run: |
          mkdir -p dist
          echo "Build output" > dist/app.js
          echo "Build artifacts" > dist/assets.tar.gz
      
      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: build-artifacts
          path: |
            dist/**
            !dist/**/*.map
          retention-days: 30
          if-no-files-found: error
  
  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: build-artifacts
          path: dist/
      
      - name: Run tests
        run: |
          ls -la dist/
          echo "Testing build artifacts..."
  
  deploy:
    needs: [build, test]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: build-artifacts
      
      - name: Deploy
        run: echo "Deploying artifacts..."

Conditional Execution

Path Filters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# .github/workflows/conditional.yml
name: Conditional Execution

on:
  push:
    paths:
      - 'src/**'
      - 'tests/**'
      - 'package.json'

jobs:
  # Sadece frontend değişirse
  frontend:
    if: contains(github.event.head_commit.message, '[frontend]')
    runs-on: ubuntu-latest
    steps:
      - run: echo "Building frontend..."
  
  # Sadece backend değişirse
  backend:
    if: contains(github.event.head_commit.message, '[backend]')
    runs-on: ubuntu-latest
    steps:
      - run: echo "Building backend..."
  
  # Path-specific trigger
  api-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: dorny/paths-filter@v3
        id: changes
        with:
          filters: |
            api:
              - 'api/**'
              - 'tests/api/**'
            frontend:
              - 'frontend/**'
      
      - name: Run API tests
        if: steps.changes.outputs.api == 'true'
        run: npm run test:api
      
      - name: Run frontend tests
        if: steps.changes.outputs.frontend == 'true'
        run: npm run test:frontend

Branch-Specific Actions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# .github/workflows/branch-specific.yml
name: Branch-Specific Workflow

on:
  push:
    branches:
      - main
      - develop
      - 'feature/**'
      - 'release/**'

jobs:
  develop-only:
    if: github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest
    steps:
      - run: echo "Running on develop branch"
  
  main-only:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - run: echo "Running on main branch"
  
  feature-branches:
    if: startsWith(github.ref, 'refs/heads/feature/')
    runs-on: ubuntu-latest
    steps:
      - run: echo "Running on feature branch"

Reusable Workflows

Workflow Template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# .github/workflows/reusable-deploy.yml
name: Reusable Deploy

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      version:
        required: false
        type: string
        default: 'latest'
    secrets:
      deploy-token:
        required: true
    outputs:
      deployment-url:
        description: "Deployment URL"
        value: $

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: $
    outputs:
      url: $
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy application
        id: deploy
        env:
          TOKEN: $
          VERSION: $
        run: |
          echo "Deploying version $VERSION to $"
          echo "url=https://$.example.com" >> $GITHUB_OUTPUT

Calling Reusable Workflow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# .github/workflows/main-deploy.yml
name: Main Deploy Pipeline

on:
  push:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test
  
  deploy-staging:
    needs: test
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: staging
      version: $
    secrets:
      deploy-token: $
  
  deploy-production:
    needs: deploy-staging
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: production
      version: $
    secrets:
      deploy-token: $
  
  notify:
    needs: deploy-production
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deployed to $"

Custom Actions

JavaScript Action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// .github/actions/hello/index.js
const core = require('@actions/core');
const github = require('@actions/github');

try {
  const nameToGreet = core.getInput('who-to-greet');
  console.log(`Hello ${nameToGreet}!`);
  
  const time = new Date().toTimeString();
  core.setOutput('time', time);
  
  const payload = JSON.stringify(github.context.payload, undefined, 2);
  console.log(`Event payload: ${payload}`);
} catch (error) {
  core.setFailed(error.message);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# .github/actions/hello/action.yml
name: 'Hello Action'
description: 'Greet someone'
inputs:
  who-to-greet:
    description: 'Who to greet'
    required: true
    default: 'World'
outputs:
  time:
    description: 'The time we greeted'
runs:
  using: 'node20'
  main: 'index.js'

Composite Action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# .github/actions/setup-app/action.yml
name: 'Setup Application'
description: 'Setup Node.js and install dependencies'

inputs:
  node-version:
    description: 'Node.js version'
    required: false
    default: '20'

runs:
  using: 'composite'
  steps:
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: $
        cache: 'npm'
    
    - name: Install dependencies
      shell: bash
      run: npm ci
    
    - name: Print versions
      shell: bash
      run: |
        node --version
        npm --version

Using Custom Action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# .github/workflows/use-custom-action.yml
name: Use Custom Actions

on: push

jobs:
  greet:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Say hello
        uses: ./.github/actions/hello
        with:
          who-to-greet: 'GitHub Actions'
      
      - name: Setup app
        uses: ./.github/actions/setup-app
        with:
          node-version: '20'
      
      - name: Build
        run: npm run build

Scheduled Workflows

Cron Jobs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# .github/workflows/scheduled.yml
name: Scheduled Tasks

on:
  schedule:
    # Her gün saat 02:00'de (UTC)
    - cron: '0 2 * * *'
    # Her Pazartesi saat 09:00'da
    - cron: '0 9 * * 1'
    # Her 6 saatte bir
    - cron: '0 */6 * * *'
  workflow_dispatch:  # Manuel tetikleme de ekle

jobs:
  daily-backup:
    runs-on: ubuntu-latest
    steps:
      - name: Backup database
        run: |
          echo "Running daily backup at $(date)"
          # Backup komutları
  
  weekly-cleanup:
    runs-on: ubuntu-latest
    if: github.event.schedule == '0 9 * * 1'
    steps:
      - name: Clean old artifacts
        run: |
          echo "Weekly cleanup at $(date)"
          # Cleanup komutları
  
  health-check:
    runs-on: ubuntu-latest
    steps:
      - name: Check service health
        run: |
          curl -f https://api.example.com/health || exit 1

Deployment Strategies

Blue-Green Deployment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# .github/workflows/blue-green-deploy.yml
name: Blue-Green Deployment

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Build new version (green)
        run: docker build -t myapp:green .
      
      - name: Deploy to green environment
        run: |
          docker stop myapp-green || true
          docker rm myapp-green || true
          docker run -d --name myapp-green -p 8081:80 myapp:green
      
      - name: Health check green
        run: |
          sleep 10
          curl -f http://localhost:8081/health || exit 1
      
      - name: Switch traffic (blue -> green)
        run: |
          # Load balancer'da trafiği green'e yönlendir
          echo "Switching traffic to green..."
          # nginx/haproxy reconfigure
      
      - name: Monitor green
        run: |
          sleep 30
          # Metrikler ve loglar kontrol et
      
      - name: Cleanup old blue
        run: |
          docker stop myapp-blue || true
          docker rm myapp-blue || true
          docker tag myapp:green myapp:blue

Canary Deployment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# .github/workflows/canary-deploy.yml
name: Canary Deployment

on:
  push:
    branches: [ main ]

jobs:
  canary-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy canary (10% traffic)
        run: |
          kubectl set image deployment/myapp myapp=myapp:$
          kubectl scale deployment/myapp-canary --replicas=1
          kubectl scale deployment/myapp-stable --replicas=9
      
      - name: Monitor canary
        run: |
          sleep 300  # 5 dakika izle
          ERROR_RATE=$(kubectl logs deployment/myapp-canary | grep ERROR | wc -l)
          if [ $ERROR_RATE -gt 10 ]; then
            echo "Canary has high error rate, rolling back..."
            exit 1
          fi
      
      - name: Increase canary traffic (50%)
        run: |
          kubectl scale deployment/myapp-canary --replicas=5
          kubectl scale deployment/myapp-stable --replicas=5
      
      - name: Monitor again
        run: sleep 300
      
      - name: Full rollout (100%)
        run: |
          kubectl set image deployment/myapp-stable myapp=myapp:$
          kubectl scale deployment/myapp-stable --replicas=10
          kubectl scale deployment/myapp-canary --replicas=0

Monitoring ve Notifications

Slack Notifications

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# .github/workflows/slack-notify.yml
name: Slack Notifications

on:
  push:
    branches: [ main ]
  pull_request:
    types: [opened, reopened]

jobs:
  notify:
    runs-on: ubuntu-latest
    steps:
      - name: Notify start
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "Deployment started for $",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*Deployment Started*\nRepository: $\nBranch: $\nActor: $"
                  }
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: $
      
      - name: Deploy
        run: echo "Deploying..."
      
      - name: Notify success
        if: success()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "Deployment successful!",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*Deployment Successful*\nRepository: $\nCommit: $"
                  }
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: $
      
      - name: Notify failure
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "❌ Deployment failed!",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*❌ Deployment Failed*\nRepository: $\nWorkflow: $\nRun: $/$/actions/runs/$"
                  }
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: $

Email Notifications

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# .github/workflows/email-notify.yml
name: Email Notifications

on:
  workflow_run:
    workflows: ["CI Pipeline"]
    types: [completed]

jobs:
  notify:
    runs-on: ubuntu-latest
    steps:
      - name: Send email on failure
        if: $
        uses: dawidd6/action-send-mail@v3
        with:
          server_address: smtp.gmail.com
          server_port: 465
          username: $
          password: $
          subject: "CI Pipeline Failed: $"
          body: |
            Workflow failed for $
            
            Branch: $
            Commit: $
            Author: $
            
            View logs: $/$/actions/runs/$
          to: [email protected]
          from: [email protected]

Best Practices

Security Best Practices

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# .github/workflows/security.yml
name: Security Best Practices

on: [push, pull_request]

jobs:
  security-checks:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
    
    steps:
      - uses: actions/checkout@v4
      
      # 1. Dependency vulnerability scanning
      - name: Run Snyk to check for vulnerabilities
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: $
      
      # 2. Code scanning (SAST)
      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: javascript, python
      
      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v3
      
      # 3. Secret scanning
      - name: Gitleaks scan
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: $
      
      # 4. Container scanning
      - name: Build Docker image
        run: docker build -t myapp:test .
      
      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: 'myapp:test'
          format: 'sarif'
          output: 'trivy-results.sarif'
      
      - name: Upload Trivy results to GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: 'trivy-results.sarif'

Performance Optimization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# .github/workflows/optimized.yml
name: Optimized Workflow

on: push

jobs:
  parallel-jobs:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        task: [lint, test, build]
    
    steps:
      - uses: actions/checkout@v4
      
      # Paralel çalışma için görevleri ayır
      - name: Run $
        run: npm run $
  
  conditional-steps:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      # Cache kullan
      - uses: actions/cache@v4
        with:
          path: ~/.npm
          key: $-npm-$
      
      # Değişen dosyalara göre çalıştır
      - uses: dorny/paths-filter@v3
        id: changes
        with:
          filters: |
            src:
              - 'src/**'
            tests:
              - 'tests/**'
      
      - name: Build (only if src changed)
        if: steps.changes.outputs.src == 'true'
        run: npm run build
      
      - name: Test (only if tests changed)
        if: steps.changes.outputs.tests == 'true'
        run: npm test

Error Handling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# .github/workflows/error-handling.yml
name: Error Handling

on: push

jobs:
  resilient-job:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      # Timeout ekle
      - name: Run tests with timeout
        timeout-minutes: 10
        run: npm test
      
      # Retry mekanizması
      - name: Deploy with retry
        uses: nick-invision/retry@v2
        with:
          timeout_minutes: 5
          max_attempts: 3
          retry_wait_seconds: 30
          command: npm run deploy
      
      # Continue on error
      - name: Optional linting
        continue-on-error: true
        run: npm run lint
      
      # Conditional execution
      - name: Cleanup on failure
        if: failure()
        run: |
          echo "Workflow failed, cleaning up..."
          docker system prune -af

Sonuç

GitHub Actions ile CI/CD pipeline’larınızı tamamen otomatikleştirebilir, kod kalitesini artırabilir ve deployment süreçlerinizi hızlandırabilirsiniz.

Key Takeaways

  • Automation: Manuel süreçleri ortadan kaldırın
  • Testing: Her commit’te otomatik test çalıştırın
  • Security: Güvenlik taramaları entegre edin
  • Monitoring: Pipeline’ları sürekli izleyin
  • Optimization: Cache ve paralel execution kullanın

Kaynaklar

CI/CD pipeline’ınızı kurarken güvenlik, performans ve maintainability’yi öncelik olarak görün!

Bu gönderi CC BY 4.0 lisansı altındadır.