Introduction

Envoy is a high-performance L7 proxy designed for microservices architectures. It powers service meshes like Istio and is widely used as an API gateway, sidecar proxy, and edge proxy. Deploying Envoy with Ansible gives you repeatable, version-controlled proxy configuration across your infrastructure.

Install Envoy

---
- name: Deploy Envoy Proxy
  hosts: proxy_servers
  become: true
  vars:
    envoy_version: "1.32.0"
  tasks:
    - name: Install dependencies
      ansible.builtin.apt:
        name:
          - apt-transport-https
          - ca-certificates
          - curl
          - gnupg
        state: present
        update_cache: true

    - name: Add Envoy GPG key
      ansible.builtin.apt_key:
        url: "https://deb.dl.getenvoy.io/public/gpg.8115BA8E629CC074.key"
        state: present

    - name: Add Envoy repository
      ansible.builtin.apt_repository:
        repo: "deb https://deb.dl.getenvoy.io/public/deb/ubuntu {{ ansible_distribution_release }} main"
        state: present

    - name: Install Envoy
      ansible.builtin.apt:
        name: "getenvoy-envoy={{ envoy_version }}*"
        state: present

    - name: Create Envoy config directory
      ansible.builtin.file:
        path: /etc/envoy
        state: directory
        owner: envoy
        group: envoy
        mode: "0755"

    - name: Deploy Envoy configuration
      ansible.builtin.template:
        src: envoy.yaml.j2
        dest: /etc/envoy/envoy.yaml
        owner: envoy
        group: envoy
        mode: "0644"
        validate: "envoy --mode validate -c %s"
      notify: restart envoy

    - name: Create systemd service
      ansible.builtin.copy:
        dest: /etc/systemd/system/envoy.service
        mode: "0644"
        content: |
          [Unit]
          Description=Envoy Proxy
          After=network.target

          [Service]
          Type=simple
          User=envoy
          ExecStart=/usr/bin/envoy -c /etc/envoy/envoy.yaml
          Restart=on-failure
          RestartSec=5
          LimitNOFILE=65536

          [Install]
          WantedBy=multi-user.target
      notify:
        - reload systemd
        - restart envoy

    - name: Enable and start Envoy
      ansible.builtin.systemd:
        name: envoy
        enabled: true
        state: started

  handlers:
    - name: reload systemd
      ansible.builtin.systemd:
        daemon_reload: true

    - name: restart envoy
      ansible.builtin.systemd:
        name: envoy
        state: restarted

Envoy Configuration Template

# templates/envoy.yaml.j2
static_resources:
  listeners:
    - name: http_listener
      address:
        socket_address:
          address: 0.0.0.0
          port_value: {{ envoy_http_port | default(8080) }}
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                stat_prefix: ingress_http
                codec_type: AUTO
                route_config:
                  name: local_route
                  virtual_hosts:
{% for vhost in envoy_virtual_hosts %}
                    - name: {{ vhost.name }}
                      domains: {{ vhost.domains | to_json }}
                      routes:
{% for route in vhost.routes %}
                        - match:
                            prefix: "{{ route.prefix }}"
                          route:
                            cluster: {{ route.cluster }}
                            timeout: {{ route.timeout | default('30s') }}
{% endfor %}
{% endfor %}
                http_filters:
                  - name: envoy.filters.http.router
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

  clusters:
{% for cluster in envoy_clusters %}
    - name: {{ cluster.name }}
      connect_timeout: {{ cluster.connect_timeout | default('5s') }}
      type: {{ cluster.discovery_type | default('STRICT_DNS') }}
      lb_policy: {{ cluster.lb_policy | default('ROUND_ROBIN') }}
      load_assignment:
        cluster_name: {{ cluster.name }}
        endpoints:
          - lb_endpoints:
{% for endpoint in cluster.endpoints %}
              - endpoint:
                  address:
                    socket_address:
                      address: {{ endpoint.address }}
                      port_value: {{ endpoint.port }}
{% endfor %}
{% if cluster.health_check is defined %}
      health_checks:
        - timeout: 5s
          interval: 10s
          unhealthy_threshold: 3
          healthy_threshold: 2
          http_health_check:
            path: "{{ cluster.health_check.path }}"
{% endif %}
{% endfor %}

admin:
  address:
    socket_address:
      address: 127.0.0.1
      port_value: {{ envoy_admin_port | default(9901) }}

TLS Termination

# host_vars/proxy1.yml
envoy_tls_enabled: true
envoy_tls_cert: /etc/envoy/tls/server.crt
envoy_tls_key: /etc/envoy/tls/server.key

envoy_virtual_hosts:
  - name: api
    domains: ["api.example.com"]
    routes:
      - prefix: "/v1/"
        cluster: api_v1
      - prefix: "/v2/"
        cluster: api_v2

envoy_clusters:
  - name: api_v1
    endpoints:
      - address: 10.0.1.10
        port: 8080
      - address: 10.0.1.11
        port: 8080
    health_check:
      path: /health
  - name: api_v2
    endpoints:
      - address: 10.0.2.10
        port: 8080
    lb_policy: LEAST_REQUEST

Troubleshooting

# Validate configuration
envoy --mode validate -c /etc/envoy/envoy.yaml

# Check admin interface
curl http://localhost:9901/clusters
curl http://localhost:9901/stats
curl http://localhost:9901/config_dump

# Check listeners
curl http://localhost:9901/listeners

# Hot restart
kill -HUP $(pgrep envoy)

Conclusion

Envoy's powerful L7 proxy features — routing, load balancing, TLS, health checking, and observability — become fully automated with Ansible. Use Jinja2 templates for dynamic configuration, handlers for zero-downtime reloads, and the admin API for health verification. This pattern scales from a single edge proxy to a fleet of sidecar proxies.