Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,24 @@
.venv
build/toopology/cEOS-Lab.tar.xz
build/topology/cEOS-Lab.tar.xz

# Runtime/generated Ansible files
automation/host_vars/*.yml
automation/collections/
automation/roles/batfish.base/
automation/validation/workshop/configs/*
automation/validation/workshop/data/bf_facts/*

# Lab-generated SSH keys
automation/demo.key
automation/demo.key.pub
build/topology/alpine-host/demo.key
build/topology/alpine-host/demo.key.pub

# Python/runtime
.venv/
__pycache__/
*.pyc

# Grafana runtime DB/data
build/monitoring/grafana/data/*
!build/monitoring/grafana/data/.gitignore
166 changes: 166 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,171 @@
# NetOps Quickstart

## 2026 Compatibility Update

This fork updates the original 2021-era **NetOps Quickstart** lab so it can run reliably in 2026 on an Ubuntu Server 20.04 VM. Ubuntu 20.04 was used intentionally because its Python 3.8 environment is a better match for the lab's older Ansible and Python dependencies than newer Ubuntu releases.

Ubuntu 22.04/24.04 may work with additional changes, but this fork was validated on Ubuntu Server 20.04 because the original lab depends on an older Python and Ansible ecosystem.

The original lab design is still useful: it demonstrates a complete NetDevOps workflow using Arista cEOS, GitLab CI, Ansible, Batfish, Prometheus, Grafana, Loki, and Docker. However, several upstream images, Python packages, Ansible collections, and Linux defaults have changed since the original project was created.

This fork preserves the original lab concept while pinning and patching the pieces that drifted.

> This is still a demo/lab environment. Do not use these settings in production.

### Tested environment

This fork was tested with:

- Ubuntu Server 20.04 VM running in VMware Workstation
- Docker Engine with legacy `docker-compose`
- Arista cEOS lab image
- GitLab CE / GitLab Runner 13.12-era stack
- Ansible 2.10-era automation environment

### Summary of fixes

| Area | Original behavior | Fix |
|---|---|---|
| GitLab CE | Used `gitlab/gitlab-ce:latest` | Pinned to `gitlab/gitlab-ce:13.12.15-ce.0` |
| GitLab Runner | Used `gitlab/gitlab-runner:latest` | Pinned to `gitlab/gitlab-runner:ubuntu-v13.12.0` |
| Consul | Used removed image `docker.io/bitnami/consul:1-debian-10` | Replaced with `hashicorp/consul:1.9.5` |
| Batfish | Used unpinned `batfish/allinone` | Pinned to `batfish/allinone:2021.04.12.882` to match the older `pybatfish` client |
| Grafana | Used `grafana/grafana:latest` | Pinned to `grafana/grafana:7.5.7` |
| Loki | Used `grafana/loki:latest` | Pinned to `grafana/loki:2.2.1` |
| Promtail | Used `grafana/promtail:latest` | Pinned to `grafana/promtail:2.2.1` |
| Arista eAPI exporter | Used `python:3-slim` and unpinned Python packages | Changed to `python:3.8-slim` and pinned compatible dependencies |
| PyYAML / yamlconfig | Newer PyYAML broke old `yamlconfig` usage | Pinned `PyYAML==5.4.1` |
| Ansible Galaxy collections | Pulled modern collections incompatible with Ansible 2.10 | Pinned compatible collection versions |
| Jinja `ipaddr` filter | Template expected an unavailable `ipaddr` filter | Replaced with `neighbor.ipv4.split('/')[0]` |
| Inventory YAML | Host entries were interpreted incorrectly without colons | Fixed host definitions such as `Leaf-1:` and `Leaf-2:` |
| Alpine host SSH | Modern Alpine/OpenSSH rejected the locked `alpine` account | Unlocked the disposable lab user during image build |
| Host ping test | Used Ansible `shell`, requiring compatible Python on Alpine hosts | Replaced with `raw` so the ping command runs directly over SSH |

### Updated Docker image pins

The following images are now pinned instead of relying on moving `latest` tags:

```yaml
gitlab/gitlab-ce:13.12.15-ce.0
gitlab/gitlab-runner:ubuntu-v13.12.0
batfish/allinone:2021.04.12.882
hashicorp/consul:1.9.5
grafana/grafana:7.5.7
grafana/loki:2.2.1
grafana/promtail:2.2.1
```

### Updated Ansible Galaxy requirements

The original lab allowed Ansible Galaxy to pull current collections into an Ansible 2.10 runtime. That caused compatibility failures.

The fixed `automation/requirements.yml` pins versions known to work with this lab:

```yaml
---
collections:
- name: lvrfrc87.git_acp
version: 2.2.0
- name: arista.eos
version: 1.3.0
- name: ansible.netcommon
version: 1.5.0

roles:
- name: batfish.base
```

### Jinja template compatibility fix

The original templates used:

```jinja2
{% set peer_ip = neighbor.ipv4 | ipaddr('address') %}
```

In the fixed version, this was replaced with:

```jinja2
{% set peer_ip = neighbor.ipv4.split('/')[0] %}
```

For this lab, the neighbor IP values are simple CIDR strings, so splitting on `/` is sufficient and avoids depending on the unavailable `ipaddr` filter.

### Alpine host SSH fix

Modern Alpine/OpenSSH refused SSH login for the `alpine` user because the account was locked:

```text
User alpine not allowed because account is locked
```

The Alpine host Dockerfile now unlocks the disposable lab user:

```dockerfile
RUN adduser -u 1000 -G wheel -s /bin/sh -D alpine && \
echo "alpine:alpine" | chpasswd && \
echo "%wheel ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
```

This is acceptable for a local disposable lab only. Do not reuse this pattern in production.

### Ping test fix

The original ping playbook used Ansible's `shell` module:

```yaml
shell: "ping -c 1 -w 2 {{ ip_to_ping }}"
```

On the Alpine host containers, this failed because Ansible attempted to execute Python-based module code on the remote host.

The fixed playbook uses `raw`:

```yaml
raw: "ping -c 1 -w 2 {{ ip_to_ping }}"
```

`raw` runs the command directly over SSH and does not require Python on the remote host.

### Files intentionally not committed

The following files are runtime artifacts or licensed/vendor images and should not be committed to this repository:

```text
build/topology/cEOS-Lab.tar.xz
automation/host_vars/*.yml
automation/collections/
automation/roles/batfish.base/
automation/demo.key
automation/demo.key.pub
build/topology/alpine-host/demo.key
build/topology/alpine-host/demo.key.pub
build/monitoring/grafana/data/*
```

Users must provide their own Arista cEOS image at:

```text
build/topology/cEOS-Lab.tar.xz
```

### Note about `logging_remote_host`

Before running the GitLab pipeline, update:

```yaml
logging_remote_host: <insert your server IP here>
```

in:

```text
automation/inventory.yml
```

Set it to the reachable IP address of your lab VM.


This is a DEMO project (thus, do not use in production environment) with the purpose of giving on overview on some example tools, methods and procedures focused on NetOps best practice.

This work is based on and is inspired by the beautiful work of [networkop](https://github.com/networkop), in his repo [https://github.com/networkop/arista-network-ci](https://github.com/networkop/arista-network-ci)
Expand Down
8 changes: 4 additions & 4 deletions automation/inventory.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ lab:
ansible_become_method: enable
ansible_httpapi_use_ssl: true
ansible_httpapi_validate_certs: false
logging_remote_host: <insert your server IP here>
logging_remote_host: <insert your server IP here> #INSERT YOUR IP HERE!
logging_remote_port: 51400
consul_host: consul
children:
Spines:
hosts:
Spine-1
Spine-1:
Leafs:
hosts:
Leaf-1
Leaf-2
Leaf-1:
Leaf-2:
testing:
vars:
ansible_become: true
Expand Down
2 changes: 1 addition & 1 deletion automation/playbooks/ping.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

tasks:
- name: Ping destination
shell: "ping -c 1 -w 2 {{ ip_to_ping }} "
raw: "ping -c 1 -w 2 {{ ip_to_ping }} "
register: output

- name: Print result
Expand Down
10 changes: 7 additions & 3 deletions automation/requirements.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
---
collections:
- lvrfrc87.git_acp
- arista.eos
- name: lvrfrc87.git_acp
version: 2.2.0
- name: arista.eos
version: 1.3.0
- name: ansible.netcommon
version: 1.5.0

roles:
- batfish.base
- name: batfish.base
2 changes: 1 addition & 1 deletion automation/roles/deploy/templates/template-config-all.j2
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ router bgp {{ bgp.asn }}
{% set rid = routerid.split('/') %}
router-id {{ rid[0] }}
{% for neighbor in bgp.neighbours %}
{% set peer_ip = neighbor.ipv4 | ipaddr('address') %}
{% set peer_ip = neighbor.ipv4.split('/')[0] %}
neighbor {{ peer_ip }} remote-as {{ neighbor.remote_asn }}
neighbor {{ peer_ip }} send-community
neighbor {{ peer_ip }} maximum-routes 12000
Expand Down
2 changes: 1 addition & 1 deletion automation/roles/validate/templates/config.j2
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ router bgp {{ bgp.asn }}
{% set rid = routerid.split('/') %}
router-id {{ rid[0] }}
{% for neighbor in bgp.neighbours %}
{% set peer_ip = neighbor.ipv4 | ipaddr('address') %}
{% set peer_ip = neighbor.ipv4.split('/')[0] %}
neighbor {{ peer_ip }} remote-as {{ neighbor.remote_asn }}
neighbor {{ peer_ip }} send-community
neighbor {{ peer_ip }} maximum-routes 12000
Expand Down
14 changes: 7 additions & 7 deletions build/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: "3"
services:

gitlab:
image: gitlab/gitlab-ce:latest
image: gitlab/gitlab-ce:13.12.15-ce.0
container_name: gitlab-lab
privileged: true
environment:
Expand Down Expand Up @@ -31,15 +31,15 @@ services:
- host-2

batfish:
image: batfish/allinone
image: batfish/allinone:2021.04.12.882
container_name: batfish
ports:
- "9996:9996"
- "9997:9997"
- "9998:9998"

loki:
image: grafana/loki:latest
image: grafana/loki:2.2.1
container_name: loki
ports:
- "3100:3100"
Expand All @@ -48,7 +48,7 @@ services:
command: -config.file=/etc/loki/loki-config.yaml

promtail:
image: grafana/promtail:latest
image: grafana/promtail:2.2.1
container_name: promtail
volumes:
- ./monitoring/promtail:/etc/promtail
Expand All @@ -58,7 +58,7 @@ services:
- "9080:9080"

grafana:
image: grafana/grafana:latest
image: grafana/grafana:7.5.7
container_name: grafana
user: root
environment:
Expand Down Expand Up @@ -99,7 +99,7 @@ services:
command: "--no-caps"

consul:
image: docker.io/bitnami/consul:1-debian-10
image: hashicorp/consul:1.9.5
container_name: consul
ports:
- '8300:8300'
Expand All @@ -118,4 +118,4 @@ networks:
name: lab_net-1
host-2:
external:
name: lab_net-2
name: lab_net-2
2 changes: 1 addition & 1 deletion build/gitlab/networkci/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM gitlab/gitlab-runner:latest
FROM gitlab/gitlab-runner:ubuntu-v13.12.0

RUN apt update && apt install -y python3 python3-pip

Expand Down
2 changes: 1 addition & 1 deletion build/monitoring/arista-eapi-exporter/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3-slim
FROM python:3.8-slim

WORKDIR /arista_exporter
COPY requirements.txt .
Expand Down
11 changes: 6 additions & 5 deletions build/monitoring/arista-eapi-exporter/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
requests
prometheus-client
falcon
yamlconfig
requests==2.25.1
prometheus-client==0.9.0
falcon==2.0.0
yamlconfig==0.3.1
PyYAML==5.4.1
argparse
pyeapi
pyeapi==0.8.4
1 change: 1 addition & 0 deletions build/topology/alpine-host/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ RUN apk update && \


RUN adduser -u 1000 -G wheel -s /bin/sh -D alpine && \
echo "alpine:alpine" | chpasswd && \
echo "%wheel ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers

COPY sshd_config /etc/ssh/sshd_config
Expand Down