Single Repository vs. Multiple Repositories

Single Repository (Monorepo) Approach:

fleet-infra/
├── clusters/
│   ├── production/
│   │   ├── flux-system/
│   │   ├── namespaces/
│   │   └── kustomization.yaml
│   └── staging/
│       ├── flux-system/
│       ├── namespaces/
│       └── kustomization.yaml
├── infrastructure/
│   ├── sources/
│   ├── monitoring/
│   ├── ingress/
│   └── cert-manager/
└── apps/
    ├── base/
    │   ├── frontend/
    │   ├── backend/
    │   └── database/
    └── overlays/
        ├── production/
        └── staging/

Benefits:

  • Single source of truth
  • Atomic changes across multiple components
  • Easier to understand the entire system
  • Simplified CI/CD setup

Drawbacks:

  • Can become large and unwieldy
  • Potential permission issues
  • May slow down Git operations

Multiple Repository Approach:

# Infrastructure Repository
infra-gitops/
├── clusters/
│   ├── production/
│   └── staging/
└── infrastructure/
    ├── monitoring/
    ├── ingress/
    └── cert-manager/

# Team A Application Repository
team-a-apps/
├── base/
│   ├── frontend/
│   └── backend/
└── overlays/
    ├── production/
    └── staging/

# Team B Application Repository
team-b-apps/
├── base/
│   └── api-service/
└── overlays/
    ├── production/
    └── staging/

Benefits:

  • Clear ownership boundaries
  • Fine-grained access control
  • Better scalability for large organizations
  • Reduced repository size

Drawbacks:

  • Coordination challenges across repositories
  • More complex CI/CD setup
  • Potential for drift between repositories

Best Practices for Repository Structure

Regardless of your approach, follow these best practices:

  1. Clear Separation of Concerns

    • Separate infrastructure from applications
    • Separate cluster-specific from shared configurations
    • Use distinct paths for different environments
  2. Use Kustomize for Environment Variations

    • Base configurations for shared elements
    • Overlays for environment-specific changes
    • Minimize duplication across environments
    apps/
    ├── base/
    │   └── frontend/
    │       ├── deployment.yaml
    │       ├── service.yaml
    │       └── kustomization.yaml
    └── overlays/
        ├── production/
        │   ├── replicas-patch.yaml
        │   └── kustomization.yaml
        └── staging/
            ├── resources-patch.yaml
            └── kustomization.yaml
    
  3. Standardize Naming Conventions

    • Consistent file and directory naming
    • Clear namespace strategies
    • Descriptive resource names
  4. Include Documentation

    • README files explaining the purpose of components
    • Architecture diagrams
    • Dependency information

Implementing GitOps Workflows

With your tools and repository structure in place, let’s explore how to implement effective GitOps workflows.

Setting Up the GitOps Pipeline

A typical GitOps pipeline involves these components:

  1. Source Code Repositories: Where application code lives
  2. CI Pipeline: Builds, tests, and pushes container images
  3. Configuration Repositories: Where Kubernetes manifests live
  4. GitOps Operator: Syncs configuration to clusters

Example Workflow with GitHub Actions and Flux:

# .github/workflows/build-and-push.yml
name: Build and Push

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
      
      - name: Update image tag in GitOps repo
        uses: actions/checkout@v3
        with:
          repository: my-org/gitops-repo
          token: ${{ secrets.PAT_TOKEN }}
          path: gitops-repo
      
      - name: Update image tag
        run: |
          cd gitops-repo
          sed -i "s|image: ghcr.io/${{ github.repository }}:.*|image: ghcr.io/${{ github.repository }}:${{ github.sha }}|" apps/base/deployment.yaml
          git config user.name "GitHub Actions"
          git config user.email "[email protected]"
          git add .
          git commit -m "Update image to ${{ github.sha }}"
          git push

Automated Image Updates with Flux

Flux can automatically update image tags in your Git repository:

# Image repository configuration
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageRepository
metadata:
  name: podinfo
  namespace: flux-system
spec:
  image: ghcr.io/stefanprodan/podinfo
  interval: 1m0s
---
# Image policy for semantic versioning
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImagePolicy
metadata:
  name: podinfo
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: podinfo
  policy:
    semver:
      range: 5.0.x
---
# Image update automation
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: flux-system
  namespace: flux-system
spec:
  interval: 1m0s
  sourceRef:
    kind: GitRepository
    name: flux-system
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        email: [email protected]
        name: fluxcdbot
      messageTemplate: '{{range .Updated.Images}}{{println .}}{{end}}'
    push:
      branch: main
  update:
    path: ./clusters/my-cluster
    strategy: Setters

Progressive Delivery with Argo Rollouts

For advanced deployment strategies, consider Argo Rollouts:

# Progressive delivery with Argo Rollouts
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: frontend
spec:
  replicas: 5
  strategy:
    canary:
      steps:
      - setWeight: 20
      - pause: {duration: 10m}
      - setWeight: 40
      - pause: {duration: 10m}
      - setWeight: 60
      - pause: {duration: 10m}
      - setWeight: 80
      - pause: {duration: 10m}
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: frontend:v1
        ports:
        - name: http
          containerPort: 8080
          protocol: TCP
        resources:
          requests:
            memory: 32Mi
            cpu: 5m

Multi-Environment GitOps

Most organizations need to manage multiple environments (development, staging, production). Here’s how to handle this with GitOps:

Environment Promotion Strategies

1. Path-Based Environments

Use different paths in the same repository for different environments:

environments/
├── dev/
│   ├── apps/
│   └── infrastructure/
├── staging/
│   ├── apps/
│   └── infrastructure/
└── production/
    ├── apps/
    └── infrastructure/

2. Branch-Based Environments

Use different branches for different environments:

  • dev branch for development
  • staging branch for staging
  • main branch for production

3. Promotion through Pull Requests

graph LR
    A[Dev Environment] -->|PR| B[Staging Environment]
    B -->|PR| C[Production Environment]

Example Promotion Workflow:

# .github/workflows/promote-to-staging.yml
name: Promote to Staging

on:
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to promote'
        required: true

jobs:
  promote:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          ref: main
      
      - name: Create promotion branch
        run: git checkout -b promote-${{ github.event.inputs.version }}-to-staging
      
      - name: Update version in staging
        run: |
          sed -i "s/tag: .*/tag: ${{ github.event.inputs.version }}/" environments/staging/apps/frontend/kustomization.yaml
          git config user.name "GitHub Actions"
          git config user.email "[email protected]"
          git add .
          git commit -m "Promote frontend ${{ github.event.inputs.version }} to staging"
          git push -u origin promote-${{ github.event.inputs.version }}-to-staging
      
      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v4
        with:
          title: "Promote frontend ${{ github.event.inputs.version }} to staging"
          body: "Automated promotion of frontend version ${{ github.event.inputs.version }} to staging environment."
          branch: promote-${{ github.event.inputs.version }}-to-staging
          base: main