Console Login

GitOps Workflows: Stop SSH-ing into Production (2020 Edition)

The Era of "kubectl apply" is Over

If you are still SSH-ing into your production servers to restart a service, or running kubectl apply -f . from your laptop, you are a security risk. I’ve seen it happen too many times: a senior engineer fat-fingers a command at 4 PM on a Friday, and suddenly the Oslo availability zone goes dark. The logs? Gone. The audit trail? Non-existent.

It is November 2020. We have better ways to manage infrastructure. With the complexity of modern microservices and the legal sledgehammer of Schrems II dropping on us this past July, manual intervention is no longer just bad practiceβ€”it's a compliance nightmare. If Datatilsynet (The Norwegian Data Protection Authority) knocks on your door, "I manually patched it" is not a valid defense.

This guide covers the implementation of a strict GitOps workflow. We aren't talking about theoretical fluff; we are talking about the architecture required to pass a strict audit while keeping your sanity intact.

The Pull Model: Security by Design

Traditional CI/CD pipelines function on a "Push" model. Jenkins or GitLab CI gets god-mode access to your cluster to deploy updates. This is a massive attack vector. If your CI server is compromised, your entire infrastructure is owned.

GitOps flips this. We use a "Pull" model. The cluster creates an outbound connection to your Git repository to check for state changes. Your CI server builds the container, pushes it to the registry, and updates a manifest in Git. That's it. It never touches the production cluster.

The Tooling: ArgoCD vs. Flux

Right now, in late 2020, the battle is between Flux and ArgoCD. While Flux has served us well, ArgoCD (currently v1.7) provides the visualization and UI that makes it easier to sell to management and junior devs. It visualizes the drift between your Git state and your Cluster state.

Implementation: The Directory Structure

Don't mix your application source code with your Kubernetes manifests. I made that mistake in 2018, and it resulted in infinite build loops. Split them.

Here is the reference architecture for a proper GitOps repository structure:

β”œβ”€β”€ apps/
β”‚   β”œβ”€β”€ base/
β”‚   β”‚   β”œβ”€β”€ deployment.yaml
β”‚   β”‚   β”œβ”€β”€ service.yaml
β”‚   β”‚   └── kustomization.yaml
β”‚   └── overlays/
β”‚       β”œβ”€β”€ production-oslo/
β”‚       β”‚   β”œβ”€β”€ kustomization.yaml
β”‚       β”‚   └── patch-replicas.yaml
β”‚       └── staging-bergen/
β”‚           β”œβ”€β”€ kustomization.yaml
β”‚           └── patch-resources.yaml
β”œβ”€β”€ cluster-config/
β”‚   β”œβ”€β”€ namespaces.yaml
β”‚   └── quotas.yaml
└── projects/
    └── ecommerce-backend.yaml

Using kustomize (built into kubectl since 1.14) allows you to maintain a base configuration and patch it for specific environments. Your production overlay might look like this:

# apps/overlays/production-oslo/patch-replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
spec:
  replicas: 5
  template:
    spec:
      containers:
      - name: payment-app
        resources:
          limits:
            memory: "2Gi"
            cpu: "1000m"

The Reconciliation Loop

Once your repo is set up, you define an Application in ArgoCD. This CRD (Custom Resource Definition) tells the controller: "Make the cluster look like this Git repo."

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: payment-service-prod
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://git.coolvds-internal.no/backend/infra-manifests.git
    targetRevision: HEAD
    path: apps/overlays/production-oslo
  destination:
    server: https://kubernetes.default.svc
    namespace: payments
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
Pro Tip: Enable selfHeal: true cautiously. If someone manually changes a setting on the cluster (drift), ArgoCD will immediately revert it. This is great for preventing "cowboy engineering," but can be annoying during active debugging.

Handling Secrets without Leaking Data

You cannot store raw passwords in Git. That is rule number one. Since we are avoiding external vaults for simplicity in this guide, the standard 2020 solution is Bitnami Sealed Secrets.

You encrypt the secret locally using a public key provided by the controller running in your cluster. Only the controller can decrypt it.

# 1. Create a raw secret (dry-run, so it doesn't apply)
kubectl create secret generic db-creds \
  --from-literal=password=SuperSecureNorwegianPassword123 \
  --dry-run=client -o yaml > secret.yaml

# 2. Seal it (encrypt it)
kubeseal --format=yaml < secret.yaml > sealed-secret.yaml

# 3. Commit sealed-secret.yaml to Git. 
# It is now safe to be public.

The Infrastructure Reality Check

GitOps is heavy on the control plane. Your GitOps controller (ArgoCD or Flux) is constantly cloning repositories, diffing YAMLs, and querying the Kubernetes API server. It generates significant I/O traffic and CPU load on your management nodes.

If you run this on cheap, oversold VPS hosting where "vCPU" basically means "a slice of a thread shared with 50 other neighbors," your reconciliation loops will lag. I've seen ArgoCD timing out simply because the underlying storage couldn't handle the etcd I/O latency.

This is why, for our internal clusters at CoolVDS, we strictly enforce KVM virtualization with NVMe storage. When the controller needs to diff 50 microservices against a Git repo, you need high IOPS. We optimized our Norwegian nodes specifically to handle the chatter of Kubernetes control planes without the "noisy neighbor" effect causing false deployment failures.

Data Sovereignty and Latency

Following the Schrems II ruling, relying on US-owned cloud providers for core infrastructure is risky. Legal teams across Oslo are scrambling to move workloads back to European soil. Hosting your GitOps control plane in Norway (e.g., via CoolVDS) ensures that the deployment logic and the secrets (even if sealed) remain within a compliant jurisdiction.

Furthermore, if your users are in Scandinavia, physics matters. Round-trip time from Oslo to a Frankfurt data center is ~25-30ms. Round-trip to a local CoolVDS instance is <5ms via NIX (Norwegian Internet Exchange). When you are running high-frequency API calls or real-time trading data, that latency reduction is not a luxury; it is a requirement.

Final Thoughts

GitOps is not just a buzzword; it is the inevitable evolution of Infrastructure as Code. It forces discipline. It creates an audit trail that satisfies GDPR requirements naturally. But it requires robust underlying hardware.

Don't let your deployment pipeline hang because your VPS provider is throttling your disk I/O. Build your platform on solid ground.

Ready to migrate your cluster to compliant, high-performance NVMe infrastructure? Deploy a KVM instance on CoolVDS today and see the difference in control plane responsiveness.