← Back to blog
NetworkSecurityDevOps

Network Engineering for DevOps Teams: What You Need to Know

· 8 min read

There is a gap in many DevOps teams. Engineers who can build sophisticated CI/CD pipelines and orchestrate containers across clusters sometimes struggle when a network issue appears. Packets get dropped, DNS resolution fails intermittently, services cannot reach each other across VPCs, and suddenly the team is waiting for “the network person” to diagnose it.

This post bridges that gap. We will walk through the networking concepts that every DevOps engineer should understand — not at CCIE depth, but deep enough to design, debug, and automate network infrastructure alongside the rest of the stack.

graph TD
    INET[Internet] --> FW[Firewall / WAF]
    FW --> LB[Load Balancer]
    LB --> APP[App Servers - Private Subnet]
    APP --> DB[(Database - Isolated Subnet)]
    APP --> MQ[Cache / MQ - Private Subnet]
    LB ~~~ VPC
    subgraph VPC[VPC 10.0.0.0/16]
        APP
        DB
        MQ
    end

VPC and VLAN Design: The Foundation

Whether you are working in AWS, Azure, or on bare metal with Proxmox (which we covered in our private cloud post), network segmentation is the starting point.

In public cloud, a VPC (Virtual Private Cloud) gives you an isolated network. The critical design decisions are:

  • CIDR block sizing — Choose a block large enough for growth but small enough to avoid overlap with other VPCs or on-premises networks. A /16 (65,536 IPs) per environment is common.
  • Subnet strategy — Separate public subnets (with internet gateway routes) from private subnets. Place load balancers in public subnets, application workloads in private subnets, and databases in isolated subnets with no internet route.
  • Availability zone distribution — Spread subnets across AZs for resilience.

A typical Terraform VPC layout:

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "production"
  cidr = "10.0.0.0/16"

  azs             = ["eu-central-1a", "eu-central-1b", "eu-central-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_nat_gateway     = true
  single_nat_gateway     = false  # one per AZ for HA
  one_nat_gateway_per_az = true

  enable_dns_hostnames = true
  enable_dns_support   = true
}

On bare metal or private cloud, VLANs serve a similar purpose. A typical VLAN scheme:

VLAN IDPurposeCIDR
10Management / IPMI10.10.0.0/24
20Production workloads10.20.0.0/22
30Storage (Ceph)10.30.0.0/24
40Kubernetes pod network10.40.0.0/16
50DMZ / Public-facing10.50.0.0/24

The key principle is the same in both worlds: segment by trust level and traffic pattern, not by team or application name.

DNS Strategy: More Important Than You Think

DNS is the most underappreciated piece of infrastructure. A solid DNS strategy provides service discovery, enables zero-downtime deployments, and simplifies multi-environment routing.

Internal DNS zones — Every environment should have a private DNS zone (e.g., prod.internal, staging.internal). In AWS, use Route 53 private hosted zones. On-premises, run CoreDNS or BIND as your internal resolver.

Split-horizon DNS — The same domain name resolves differently depending on where the query originates. Internal clients get private IPs; external clients get public IPs.

TTLs matter — Before a migration or failover, lower TTLs to 60 seconds. After the change stabilizes, raise them back to 300-3600 seconds to reduce DNS query volume.

DNS as code — Manage DNS records in Terraform or a GitOps workflow, never by clicking in a console:

resource "aws_route53_record" "api" {
  zone_id = aws_route53_zone.internal.zone_id
  name    = "api.prod.internal"
  type    = "A"

  alias {
    name                   = aws_lb.api.dns_name
    zone_id                = aws_lb.api.zone_id
    evaluate_target_health = true
  }
}

Load Balancing: Layers and Patterns

Understanding the difference between L4 and L7 load balancing is essential:

  • L4 (TCP/UDP) — Forwards packets based on IP and port. Fast, simple, protocol-agnostic. Use for databases, gRPC, non-HTTP services. AWS NLB, HAProxy in TCP mode.
  • L7 (HTTP/HTTPS) — Inspects HTTP headers, paths, cookies. Enables path-based routing, header injection, rate limiting. Use for web applications and APIs. AWS ALB, Nginx, Traefik.

In Kubernetes, this maps to:

  • Service type: LoadBalancer — Provisions an L4 load balancer (usually cloud-provider NLB)
  • Ingress / Gateway API — L7 routing with rules for host, path, headers

A common mistake is using an L7 load balancer for everything. L7 inspection adds latency and limits throughput. For high-throughput internal services (database replicas, message queues), L4 is the right choice.

Firewall Rules as Code

Traditional firewalls are configured through web GUIs, and changes are tracked in spreadsheets or ticketing systems. This does not scale and it does not integrate with DevOps workflows.

The modern approach: firewall rules as code, version-controlled and applied through CI/CD.

In cloud environments, security groups and network ACLs are already defined in Terraform. On-premises, tools like nftables can be templated and deployed with Ansible:

# ansible role: firewall/tasks/main.yml
- name: Deploy nftables rules
  ansible.builtin.template:
    src: nftables.conf.j2
    dest: /etc/nftables.conf
    owner: root
    mode: '0600'
  notify: restart nftables

- name: Ensure nftables is running
  ansible.builtin.systemd:
    name: nftables
    state: started
    enabled: true
# templates/nftables.conf.j2
table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;

    # Allow established connections
    ct state established,related accept

    # Allow loopback
    iif lo accept

    # Allow SSH from management VLAN only
    ip saddr 10.10.0.0/24 tcp dport 22 accept

    # Allow HTTP/HTTPS from DMZ
    ip saddr 10.50.0.0/24 tcp dport { 80, 443 } accept

    {% for rule in custom_firewall_rules %}
    {{ rule }}
    {% endfor %}

    # Log and drop everything else
    log prefix "nftables-drop: " drop
  }
}

This approach gives you version history, peer review, and automated testing of firewall changes — the same workflow you use for application code. For a deeper dive into security automation, see our Kubernetes security hardening post.

Service Mesh: When You Need It (and When You Do Not)

A service mesh (Istio, Linkerd, Cilium) adds a proxy sidecar to every pod, giving you mutual TLS, traffic management, and observability without changing application code. The benefits are real:

  • mTLS everywhere — Encrypted service-to-service communication with automatic certificate rotation
  • Traffic splitting — Canary deployments at the network layer
  • Observability — Request-level metrics, distributed tracing, access logs

But the cost is also real: increased resource consumption (each sidecar uses CPU and memory), added latency (every request goes through two proxies), and significant operational complexity.

Our recommendation: Start without a service mesh. Use Kubernetes NetworkPolicies for segmentation and cert-manager for TLS. Only add a service mesh when you have a concrete need — usually mTLS compliance requirements or sophisticated traffic management — and the team capacity to operate it.

Network Observability

You cannot troubleshoot what you cannot see. A minimal network observability stack includes:

  1. Flow logs — AWS VPC Flow Logs, or conntrack and tcpdump on bare metal. These tell you what is talking to what.
  2. DNS query logging — Log and monitor DNS queries to catch misconfigurations and identify services calling unexpected endpoints.
  3. Latency monitoring — Track p50/p95/p99 latency between services. Tools like Prometheus with blackbox_exporter or Cilium Hubble.
  4. Packet capture capability — Have tcpdump and Wireshark skills on the team. When things get weird, there is no substitute for reading packets.

A quick Prometheus alert for detecting network issues:

groups:
  - name: network
    rules:
      - alert: HighDNSLatency
        expr: histogram_quantile(0.95, rate(dns_lookup_duration_seconds_bucket[5m])) > 0.1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "DNS resolution p95 latency above 100ms"

      - alert: InterfaceErrors
        expr: rate(node_network_receive_errs_total[5m]) > 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Network interface {{ $labels.device }} is receiving errors"

Debugging Checklist

When a network issue hits, work through the layers systematically:

  1. Is the host up?ping, check if the machine is reachable at all
  2. Is the port open?nc -zv host port or ss -tlnp on the target
  3. Is DNS resolving correctly?dig or nslookup, check for stale records
  4. Is traffic being blocked? — Check security groups, nftables, NetworkPolicies
  5. Is the route correct?traceroute, check routing tables, VPC route tables
  6. Is the application listening?curl -v, check logs for bind errors

This is methodical, not magical. The most common “network problems” turn out to be DNS caching, misconfigured security groups, or applications binding to localhost instead of 0.0.0.0.

Conclusion

Networking is not a separate discipline from DevOps — it is a core part of it. Every deployment, every service communication, every security boundary relies on the network. By treating network configuration as code, understanding the fundamentals of segmentation and routing, and investing in observability, DevOps teams can own the full stack confidently.

If your team is building a hybrid cloud architecture, the networking layer is where the complexity lives. Getting it right from the start saves months of debugging later.