<p><strong>Meta Description:</strong> Aprenda GitOps: infraestrutura como código, testes de infraestrutura, Terraform, Ansible e como garantir qualidade em configs.</p>

<hr>
<h2>O Que é GitOps?</h2>

<p>GitOps usa Git como fonte única de verdade para infraestrutura e configurações. Changes são feitos via Git commits, não manualmente.</p>

<h3>Princípios</h3>
<ol><li><strong>Tudo no Git</strong> - Código, configs, infraestrutura</li>
<li><strong>Automação</strong> - CI/CD reconcile com Git</li>
<li><strong>Idempotência</strong> - mesmo resultado sempre</li>
<li><strong>Auditoria</strong> - histórico completo de mudanças</li>
</ol>
<hr>
<h2>Terraform - Infraestrutura como Código</h2>

<h3>Setup</h3>

<pre><code># main.tf
terraform {
  required_version = "&gt;= 1.0"

<p>required_providers {<br>    aws = {<br>      source  = "hashicorp/aws"<br>      version = "~&gt; 5.0"<br>    }<br>  }</p>

<p>backend "s3" {<br>    bucket = "my-terraform-state"<br>    key    = "prod/terraform.tfstate"<br>    region = "us-east-1"<br>  }<br>}</p>

<p>provider "aws" {<br>  region = var.aws_region<br>}</p>

<p># Variables<br>variable "aws_region" {<br>  description = "AWS region"<br>  type        = string<br>  default     = "us-east-1"<br>}</p>

<p>variable "environment" {<br>  description = "Environment name"<br>  type        = string<br>  validation {<br>    condition     = contains(["dev", "staging", "prod"], var.environment)<br>    error_message = "Environment must be dev, staging, or prod."<br>  }<br>}<br></code></pre></p>

<h3>Recursos</h3>

<pre><code># modules/api-service/main.tf
variable "environment" {}
variable "app_name" {}
variable "desired_count" { default = 2 }
variable "instance_type" { default = "t3.micro" }

<p>resource "aws_ecs_cluster" "main" {<br>  name = "${var.app_name}-${var.environment}"</p>

<p>setting {<br>    name  = "containerInsights"<br>    value = "enabled"<br>  }<br>}</p>

<p>resource "aws_ecs_task_definition" "api" {<br>  family                   = "${var.app_name}-api"<br>  network_mode             = "awsvpc"<br>  requires_compatibilities = ["FARGATE"]<br>  cpu                      = "256"<br>  memory                   = "512"</p>

<p>container_definitions = jsonencode([<br>    {<br>      name      = "api"<br>      image     = "${var.app_name}:latest"<br>      essential = true</p>

<p>portMappings = [{<br>        containerPort = 8080<br>        protocol      = "tcp"<br>      }]</p>

<p>environment = [<br>        { name = "ENV", value = var.environment }<br>      ]</p>

<p>logConfiguration = {<br>        logDriver = "awslogs"<br>        options = {<br>          "awslogs-group"         = "/ecs/${var.app_name}"<br>          "awslogs-region"        = "us-east-1"<br>          "awslogs-stream-prefix" = "ecs"<br>        }<br>      }<br>    }<br>  ])<br>}</p>

<p>resource "aws_ecs_service" "api" {<br>  name            = "${var.app_name}-api-${var.environment}"<br>  cluster         = aws_ecs_cluster.main.id<br>  task_definition = aws_ecs_task_definition.api.arn<br>  desired_count   = var.desired_count</p>

<p>deployment_controller {<br>    type = "ECS"<br>  }</p>

<p>deployment_maximum_percent         = 200<br>  deployment_minimum_healthy_percent = 100</p>

<p>load_balancer {<br>    target_group_arn = aws_lb_target_group.api.arn<br>    container_name   = "api"<br>    container_port   = 8080<br>  }</p>

<p>depends_on = [aws_lb.api]<br>}<br></code></pre></p>

<hr>
<h2>Testando Infraestrutura</h2>

<h3>Terratest (Go)</h3>

<pre><code>// api_service_test.go
package test

<p>import (<br>    "testing"<br>    "github.com/gruntwork-io/terratest/modules/terraform"<br>    "github.com/stretchr/testify/assert"<br>    "github.com/gruntwork-io/terratest/modules/aws"<br>    "time"<br>)</p>

<p>func TestAPIService(t *testing.T) {<br>    // Setup<br>    terraformOptions := &amp;terraform.Options{<br>        TerraformDir: "../modules/api-service",<br>        Vars: map[string]interface{}{<br>            "environment": "test",<br>            "app_name":    "myapp",<br>            "desired_count": 2,<br>        },<br>        BackendConfig: map[string]interface{}{<br>            "bucket": "my-test-tfstate",<br>            "key":    "test/terraform.tfstate",<br>        },<br>    }</p>

<p>defer terraform.Destroy(t, terraformOptions)<br>    terraform.InitAndApply(t, terraformOptions)</p>

<p>// Get outputs<br>    clusterName := terraform.Output(t, terraformOptions, "cluster_name")<br>    serviceName := terraform.Output(t, terraformOptions, "service_name")</p>

<p>// Assertions<br>    assert.NotEmpty(t, clusterName)<br>    assert.NotEmpty(t, serviceName)</p>

<p>// Wait for service to be stable<br>    aws.WaitForServiceToStabilize(t, "us-east-1", clusterName, serviceName, 10*time.Minute)</p>

<p>// Get service details<br>    service := aws.GetEcsServiceDetails(t, "us-east-1", clusterName, serviceName)<br>    assert.Equal(t, int32(2), *service.DesiredCount)</p>

<p>// Verify load balancer<br>    lbArn := terraform.Output(t, terraformOptions, "lb_arn")<br>    assert.NotEmpty(t, lbArn)<br>}<br></code></pre></p>

<h3>Checkov (Security Scanning)</h3>

<pre><code># .github/workflows/security-scan.yml
name: Infrastructure Security Scan

<p>on:<br>  pull_request:<br>    paths:<br>      - '**.tf'<br>      - '<strong>/terrform/</strong>'</p>

<p>jobs:<br>  security-scan:<br>    runs-on: ubuntu-latest<br>    steps:<br>      - uses: actions/checkout@v4</p>

<p>- name: Run Checkov<br>        uses: bridgecrewio/checkov-action@master<br>        with:<br>          directory: .<br>          framework: terraform<br>          output_format: sarif<br>          output_file_path: results.sarif</p>

<p>- name: Upload to GitHub Security<br>        uses: github/codeql-action/upload-sarif@v2<br>        with:<br>          sarif_file: results.sarif<br></code></pre></p>

<pre><code># CLI
checkov -d ./infrastructure --framework terraform

<p># Com fail<br>checkov -d ./infrastructure --check CK_VPC_SG_1</p>

<p># Skip específico<br>checkov -d ./infrastructure --skip-check CK_VPC_SG_1<br></code></pre></p>

<h3>Sentinel (Policy as Code)</h3>

<pre><code># policies/api-service.sentinel
import "tfplan/v2" as tfplan

<p># Validate environment<br>validate_environment = func() {<br>    violations = []</p>

<p>for tfplan.resource_changes as rc {<br>        if rc.type is "aws_ecs_service" {<br>            environment = rc.change.after.environment<br>            if environment is "production" {<br>                desired_count = rc.change.after.desired_count<br>                if desired_count &lt; 2 {<br>                    append(violations, rc.name)<br>                }<br>            }<br>        }<br>    }</p>

<p>return violations<br>}</p>

<p>main = rule {<br>    validate_environment() is []<br>}<br></code></pre></p>

<hr>
<h2>Ansible - Configuração</h2>

<h3>Playbook</h3>

<pre><code># playbooks/deploy-api.yml
<hr>
<ul><li>name: Deploy API Service</li></ul>
  hosts: api_servers
  become: yes
  vars:
    app_version: "1.2.3"
    deploy_path: "/opt/myapp"

<p>tasks:<br>    - name: Ensure deploy directory exists<br>      file:<br>        path: "{{ deploy_path }}"<br>        state: directory<br>        owner: www-data<br>        group: www-data<br>        mode: '0755'</p>

<p>- name: Deploy application<br>      template:<br>        src: "app.conf.j2"<br>        dest: "{{ deploy_path }}/config.conf"<br>        owner: www-data<br>        group: www-data<br>        mode: '0644'<br>      notify: Restart API</p>

<p>- name: Pull latest image<br>      docker_image:<br>        name: myapp/api<br>        tag: "{{ app_version }}"<br>        source: pull<br>        force_source: yes</p>

<p>- name: Ensure API is running<br>      docker_container:<br>        name: api<br>        image: "myapp/api:{{ app_version }}"<br>        state: started<br>        restart_policy: always<br>        ports:<br>          - "8080:8080"<br>        env:<br>          ENV: "{{ app_environment }}"<br>          DATABASE_URL: "{{ db_connection_string }}"<br>        volumes:<br>          - "{{ deploy_path }}/config.conf:/app/config.conf:ro"</p>

<p>- name: Wait for API to be healthy<br>      uri:<br>        url: "http://localhost:8080/health"<br>        status_code: 200<br>      register: result<br>      until: result.status == 200<br>      retries: 30<br>      delay: 5</p>

<p>handlers:<br>    - name: Restart API<br>      docker_container:<br>        name: api<br>        state: restarted<br></code></pre></p>

<h3>Molecule (Testing Ansible)</h3>

<pre><code># molecule/default/molecule.yml
<hr>
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
</code></pre>

<pre><code># molecule/default/tests/test_default.py
import os
import pytest

<p>def test_api_installed(host):<br>    api = host.file("/opt/myapp")<br>    assert api.exists<br>    assert api.is_directory</p>

<p>def test_api_config(host):<br>    config = host.file("/opt/myapp/config.conf")<br>    assert config.exists<br>    assert config.user == "www-data"</p>

<p>def test_api_service(host):<br>    service = host.service("api")<br>    assert service.is_running<br>    assert service.is_enabled</p>

<p>def test_api_health(host):<br>    response = host.run("curl -f http://localhost:8080/health")<br>    assert response.rc == 0<br>    assert "healthy" in response.stdout<br></code></pre></p>

<hr>
<h2>ArgoCD - GitOps</h2>

<h3>Application</h3>

<pre><code># 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
</code></pre>

<hr>
<h2>Conclusão</h2>

<p>GitOps garante infraestrutura versionada e testável. As chaves são:<br><ol><li><strong>Tudo no Git</strong> - Audit trail completo</li><br><li><strong>Testar infraestrutura</strong> - Terratest, Checkov, Molecule</li><br><li><strong>Automação</strong> - ArgoCD reconcile</li><br><li><strong>Policy as Code</strong> - Validate with Sentinel</li><br><li><strong>Drift detection</strong> - Detect unauthorized changes</li></ol></p>