O Que é GitOps?
GitOps usa Git como fonte única de verdade para infraestrutura e configurações. Changes são feitos via Git commits, não manualmente.
Princípios
- Tudo no Git – Código, configs, infraestrutura
- Automação – CI/CD reconcile com Git
- Idempotência – mesmo resultado sempre
- Auditoria – histórico completo de mudanças
Terraform – Infraestrutura como Código
Setup
# main.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
}
}
provider "aws" {
region = var.aws_region
}
# Variables
variable "aws_region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "environment" {
description = "Environment name"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
Recursos
# modules/api-service/main.tf
variable "environment" {}
variable "app_name" {}
variable "desired_count" { default = 2 }
variable "instance_type" { default = "t3.micro" }
resource "aws_ecs_cluster" "main" {
name = "${var.app_name}-${var.environment}"
setting {
name = "containerInsights"
value = "enabled"
}
}
resource "aws_ecs_task_definition" "api" {
family = "${var.app_name}-api"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "256"
memory = "512"
container_definitions = jsonencode([
{
name = "api"
image = "${var.app_name}:latest"
essential = true
portMappings = [{
containerPort = 8080
protocol = "tcp"
}]
environment = [
{ name = "ENV", value = var.environment }
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = "/ecs/${var.app_name}"
"awslogs-region" = "us-east-1"
"awslogs-stream-prefix" = "ecs"
}
}
}
])
}
resource "aws_ecs_service" "api" {
name = "${var.app_name}-api-${var.environment}"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.api.arn
desired_count = var.desired_count
deployment_controller {
type = "ECS"
}
deployment_maximum_percent = 200
deployment_minimum_healthy_percent = 100
load_balancer {
target_group_arn = aws_lb_target_group.api.arn
container_name = "api"
container_port = 8080
}
depends_on = [aws_lb.api]
}
Testando Infraestrutura
Terratest (Go)
// api_service_test.go
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
"github.com/gruntwork-io/terratest/modules/aws"
"time"
)
func TestAPIService(t *testing.T) {
// Setup
terraformOptions := &terraform.Options{
TerraformDir: "../modules/api-service",
Vars: map[string]interface{}{
"environment": "test",
"app_name": "myapp",
"desired_count": 2,
},
BackendConfig: map[string]interface{}{
"bucket": "my-test-tfstate",
"key": "test/terraform.tfstate",
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
// Get outputs
clusterName := terraform.Output(t, terraformOptions, "cluster_name")
serviceName := terraform.Output(t, terraformOptions, "service_name")
// Assertions
assert.NotEmpty(t, clusterName)
assert.NotEmpty(t, serviceName)
// Wait for service to be stable
aws.WaitForServiceToStabilize(t, "us-east-1", clusterName, serviceName, 10*time.Minute)
// Get service details
service := aws.GetEcsServiceDetails(t, "us-east-1", clusterName, serviceName)
assert.Equal(t, int32(2), *service.DesiredCount)
// Verify load balancer
lbArn := terraform.Output(t, terraformOptions, "lb_arn")
assert.NotEmpty(t, lbArn)
}
Checkov (Security Scanning)
# .github/workflows/security-scan.yml
name: Infrastructure Security Scan
on:
pull_request:
paths:
- '**.tf'
- '/terrform/'
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: .
framework: terraform
output_format: sarif
output_file_path: results.sarif
- name: Upload to GitHub Security
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: results.sarif
# CLI
checkov -d ./infrastructure --framework terraform
# Com fail
checkov -d ./infrastructure --check CK_VPC_SG_1
# Skip específico
checkov -d ./infrastructure --skip-check CK_VPC_SG_1
Sentinel (Policy as Code)
# policies/api-service.sentinel
import "tfplan/v2" as tfplan
# Validate environment
validate_environment = func() {
violations = []
for tfplan.resource_changes as rc {
if rc.type is "aws_ecs_service" {
environment = rc.change.after.environment
if environment is "production" {
desired_count = rc.change.after.desired_count
if desired_count < 2 {
append(violations, rc.name)
}
}
}
}
return violations
}
main = rule {
validate_environment() is []
}
Ansible – Configuração
Playbook
# playbooks/deploy-api.yml
- name: Deploy API Service
hosts: api_servers
become: yes
vars:
app_version: "1.2.3"
deploy_path: "/opt/myapp"
tasks:
- name: Ensure deploy directory exists
file:
path: "{{ deploy_path }}"
state: directory
owner: www-data
group: www-data
mode: '0755'
- name: Deploy application
template:
src: "app.conf.j2"
dest: "{{ deploy_path }}/config.conf"
owner: www-data
group: www-data
mode: '0644'
notify: Restart API
- name: Pull latest image
docker_image:
name: myapp/api
tag: "{{ app_version }}"
source: pull
force_source: yes
- name: Ensure API is running
docker_container:
name: api
image: "myapp/api:{{ app_version }}"
state: started
restart_policy: always
ports:
- "8080:8080"
env:
ENV: "{{ app_environment }}"
DATABASE_URL: "{{ db_connection_string }}"
volumes:
- "{{ deploy_path }}/config.conf:/app/config.conf:ro"
- name: Wait for API to be healthy
uri:
url: "http://localhost:8080/health"
status_code: 200
register: result
until: result.status == 200
retries: 30
delay: 5
handlers:
- name: Restart API
docker_container:
name: api
state: restarted
Molecule (Testing Ansible)
# molecule/default/molecule.yml
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: instance
image: "geerlingguy/docker-${MOLECULE_DISTRO:-centos7}"
command: ""
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
privileged: true
provisioner:
name: ansible
lint: |
ansible-lint
verifier:
name: testinfra
lint:
- flake8
# molecule/default/tests/test_default.py
import os
import pytest
def test_api_installed(host):
api = host.file("/opt/myapp")
assert api.exists
assert api.is_directory
def test_api_config(host):
config = host.file("/opt/myapp/config.conf")
assert config.exists
assert config.user == "www-data"
def test_api_service(host):
service = host.service("api")
assert service.is_running
assert service.is_enabled
def test_api_health(host):
response = host.run("curl -f http://localhost:8080/health")
assert response.rc == 0
assert "healthy" in response.stdout
ArgoCD – GitOps
Application
# argocd/application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-api
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/myorg/manifests.git
targetRevision: HEAD
path: apps/api-service/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- PruneLast=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
Conclusão
GitOps garante infraestrutura versionada e testável. As chaves são:
- Tudo no Git – Audit trail completo
- Testar infraestrutura – Terratest, Checkov, Molecule
- Automação – ArgoCD reconcile
- Policy as Code – Validate with Sentinel
- Drift detection – Detect unauthorized changes
