🚀 GitHub Actions Pipeline Kurulum Rehberi#
📁 ADIM 1: Repository Yapısını Organize Et#
Proje Klasör Yapısı:#
my-devops-project/
├── .github/
│ └── workflows/
│ ├── ci-cd.yml
│ └── cleanup.yml
├── k8s/
│ ├── base/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ ├── ingress.yaml
│ │ └── kustomization.yaml
│ ├── staging/
│ │ └── kustomization.yaml
│ └── production/
│ ├── cluster-a/
│ │ └── kustomization.yaml
│ └── cluster-b/
│ └── kustomization.yaml
├── src/
│ ├── app.js (örnek uygulama)
│ ├── package.json
│ └── Dockerfile
├── scripts/
│ ├── health-check.sh
│ └── deploy.sh
├── README.md
└── .dockerignore
🐳 ADIM 2: Örnek Uygulama ve Dockerfile Hazırla#
Basit Node.js Uygulaması (src/app.js):#
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
// Health check endpoints
app.get('/health', (req, res) => {
res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString() });
});
app.get('/ready', (req, res) => {
res.status(200).json({ status: 'ready', timestamp: new Date().toISOString() });
});
app.get('/', (req, res) => {
res.json({
message: 'Hello from DevOps Pipeline!',
version: process.env.APP_VERSION || '1.0.0',
cluster: process.env.CLUSTER_NAME || 'unknown'
});
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
src/package.json:#
{
"name": "devops-demo-app",
"version": "1.0.0",
"description": "Demo app for DevOps pipeline",
"main": "app.js",
"scripts": {
"start": "node app.js",
"test": "echo \"No tests yet\" && exit 0"
},
"dependencies": {
"express": "^4.18.2"
}
}
Optimized Dockerfile (src/Dockerfile):#
# Multi-stage build for smaller image
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:18-alpine AS production
WORKDIR /app
# Security: Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
# Copy application
COPY --from=builder /app/node_modules ./node_modules
COPY --chown=nextjs:nodejs . .
# Security: Run as non-root
USER nextjs
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
CMD ["npm", "start"]
.dockerignore:#
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
coverage
.nyc_output
.DS_Store
.vscode
.github
k8s
scripts
⚙️ ADIM 3: Kubernetes Manifests Hazırla#
Base Deployment (k8s/base/deployment.yaml):#
apiVersion: apps/v1
kind: Deployment
metadata:
name: devops-app
labels:
app: devops-app
spec:
replicas: 3
selector:
matchLabels:
app: devops-app
template:
metadata:
labels:
app: devops-app
spec:
containers:
- name: app
image: ghcr.io/USERNAME/devops-app:latest # Bu kısmı kendi username'inle değiştir
ports:
- containerPort: 3000
env:
- name: APP_VERSION
value: "1.0.0"
- name: CLUSTER_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
startupProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 30
Service (k8s/base/service.yaml):#
apiVersion: v1
kind: Service
metadata:
name: devops-app-service
labels:
app: devops-app
spec:
selector:
app: devops-app
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: ClusterIP
Ingress (k8s/base/ingress.yaml):#
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: devops-app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
rules:
- host: devops-app.local # Test için local host
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: devops-app-service
port:
number: 80
Base Kustomization (k8s/base/kustomization.yaml):#
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- ingress.yaml
commonLabels:
environment: base
project: devops-demo
Staging Kustomization (k8s/staging/kustomization.yaml):#
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: staging
resources:
- ../base
patchesStrategicMerge:
- deployment-patch.yaml
commonLabels:
environment: staging
images:
- name: ghcr.io/USERNAME/devops-app
newTag: staging-latest
Staging Deployment Patch (k8s/staging/deployment-patch.yaml):#
apiVersion: apps/v1
kind: Deployment
metadata:
name: devops-app
spec:
replicas: 2
template:
spec:
containers:
- name: app
env:
- name: ENVIRONMENT
value: "staging"
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
🔧 ADIM 4: GitHub Actions Workflow Oluştur#
Ana CI/CD Pipeline (.github/workflows/ci-cd.yml):#
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-test:
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
image-digest: ${{ steps.build.outputs.digest }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: src/package-lock.json
- name: Install dependencies
run: |
cd src
npm ci
- name: Run tests
run: |
cd src
npm test
- name: Generate version
id: version
run: |
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
VERSION="v$(date +%Y%m%d)-${GITHUB_SHA:0:7}"
else
VERSION="${{ github.ref_name }}-${GITHUB_SHA:0:7}"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Generated version: $VERSION"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=raw,value=${{ steps.version.outputs.version }}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
id: build
uses: docker/build-push-action@v5
with:
context: ./src
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Sign container image
run: |
echo "Container image built and pushed successfully!"
echo "Tags: ${{ steps.meta.outputs.tags }}"
deploy-staging:
needs: build-and-test
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
environment: staging
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup kubectl
uses: azure/setup-kubectl@v3
with:
version: 'v1.28.0'
- name: Setup Kustomize
run: |
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
sudo mv kustomize /usr/local/bin/
- name: Setup kubeconfig for staging
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG_STAGING }}" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
- name: Create staging namespace
run: |
kubectl create namespace staging --dry-run=client -o yaml | kubectl apply -f -
- name: Deploy to staging
run: |
cd k8s/staging
kustomize edit set image ghcr.io/${{ github.repository }}=${{ needs.build-and-test.outputs.image-tag }}
kustomize build . | kubectl apply -f -
- name: Wait for deployment
run: |
kubectl rollout status deployment/devops-app -n staging --timeout=300s
- name: Verify deployment
run: |
kubectl get pods -n staging
kubectl get services -n staging
- name: Run health check
run: |
# Pod seviyesinde health check
kubectl exec -n staging deployment/devops-app -- curl -f http://localhost:3000/health
echo "✅ Staging deployment successful!"
deploy-production:
needs: build-and-test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
strategy:
matrix:
cluster: [cluster-a, cluster-b]
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup kubectl
uses: azure/setup-kubectl@v3
with:
version: 'v1.28.0'
- name: Setup Kustomize
run: |
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
sudo mv kustomize /usr/local/bin/
- name: Setup kubeconfig for ${{ matrix.cluster }}
run: |
mkdir -p ~/.kube
if [[ "${{ matrix.cluster }}" == "cluster-a" ]]; then
echo "${{ secrets.KUBECONFIG_PROD_A }}" | base64 -d > ~/.kube/config
else
echo "${{ secrets.KUBECONFIG_PROD_B }}" | base64 -d > ~/.kube/config
fi
chmod 600 ~/.kube/config
- name: Create production namespace
run: |
kubectl create namespace production --dry-run=client -o yaml | kubectl apply -f -
- name: Deploy to ${{ matrix.cluster }}
run: |
cd k8s/production/${{ matrix.cluster }}
kustomize edit set image ghcr.io/${{ github.repository }}=${{ needs.build-and-test.outputs.image-tag }}
kustomize build . | kubectl apply -f -
- name: Wait for deployment on ${{ matrix.cluster }}
run: |
kubectl rollout status deployment/devops-app -n production --timeout=300s
- name: Verify deployment on ${{ matrix.cluster }}
run: |
kubectl get pods -n production
kubectl get services -n production
- name: Health check on ${{ matrix.cluster }}
run: |
kubectl exec -n production deployment/devops-app -- curl -f http://localhost:3000/health
echo "✅ Production deployment successful on ${{ matrix.cluster }}!"
- name: Post-deployment notification
if: success()
run: |
echo "🚀 Successfully deployed to ${{ matrix.cluster }}"
echo "Version: ${{ needs.build-and-test.outputs.version }}"
echo "Image: ${{ needs.build-and-test.outputs.image-tag }}"
cleanup:
needs: [deploy-staging, deploy-production]
if: always()
runs-on: ubuntu-latest
steps:
- name: Cleanup old images
run: |
echo "🧹 Cleanup job completed (placeholder for future image cleanup logic)"
🔑 ADIM 5: GitHub Secrets Kurulumu#
Repository Settings → Secrets and Variables → Actions#
Aşağıdaki secret'ları eklemen gerekiyor:
1. Kubeconfig Files:#
# Staging cluster için kubeconfig'i base64 encode et
cat ~/.kube/config | base64 -w 0
# Çıktıyı KUBECONFIG_STAGING olarak ekle
# Production cluster-a için (aynı işlemi production cluster'ınla tekrarla)
# KUBECONFIG_PROD_A olarak ekle
# Production cluster-b için (ikinci production cluster kurulduğunda)
# KUBECONFIG_PROD_B olarak ekle
2. Environment Variables (Opsiyonel):#
SLACK_WEBHOOK_URL: Bildirimler içinDOCKER_REGISTRY_PASSWORD: Ekstra güvenlik için
🚀 ADIM 6: İlk Deployment Test#
1. Repository'yi Oluştur ve Push Et:#
# GitHub'da yeni repo oluştur: my-devops-project
# Local'de initialize et
git init
git add .
git commit -m "Initial DevOps pipeline setup"
git branch -M main
git remote add origin https://github.com/USERNAME/my-devops-project.git
git push -u origin main
# Development branch oluştur
git checkout -b develop
git push -u origin develop
2. Kubernetes'te Ingress Controller Kurulumu:#
# NGINX Ingress Controller kur
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.1/deploy/static/provider/baremetal/deploy.yaml
# Ingress controller'ın çalıştığını kontrol et
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
3. Test Deployment:#
# Develop branch'e push yaparak staging deploy'u tetikle
echo "Test change" >> README.md
git add README.md
git commit -m "Test staging deployment"
git push origin develop
# GitHub Actions'ı izle
# https://github.com/USERNAME/my-devops-project/actions
4. Local Test:#
# Staging'e deploy edildikten sonra test et
kubectl get pods -n staging
kubectl get svc -n staging
# Port forward ile test
kubectl port-forward -n staging service/devops-app-service 8080:80
# Browser'da http://localhost:8080 aç
# Ya da curl ile test et
curl http://localhost:8080
curl http://localhost:8080/health
🚨 Troubleshooting#
GitHub Actions Hataları:#
# Actions sekmesinde hata görürsen, logs'u kontrol et
# Yaygın hatalar:
# 1. Kubeconfig secret eksik/yanlış
# 2. Image tag formatting hataları
# 3. Kubernetes namespace yoksa
# Debug için manuel test:
kubectl auth can-i get pods --namespace staging
kubectl get nodes