使用GitLab和Ansible构建网络感知CI/CD流水线实战指南

本文详细讲解如何利用GitLab和Ansible构建具备网络感知能力的CI/CD流水线,包含完整的环境搭建、配置步骤和自动化测试流程,确保网络变更的安全性和可回滚性。

构建网络感知CI/CD流水线:GitLab与Ansible实战

自动化已成为企业网络管理的重要组成部分。无论网络采用何种自动化方式,其CI/CD流水线都应具备网络感知能力。

在网络感知CI/CD流水线中,意味着在推送新配置时需要检查现网关键配置。了解如何构建网络自动化需求以进行现网状态检查,有助于确保每个变更都经过验证且可回滚。Ansible剧本、Python或GoLang代码可以验证现网状态,并使用GitLab、Jenkins或GitHub Actions等工具执行。

CI/CD操作流程

要使CI/CD流水线具备网络感知能力,网络团队必须执行以下步骤:

  1. 预检查:对现网执行初始查询,例如检查先前配置的关键接口是否正常运行
  2. 部署:向网络应用新配置
  3. 后检查:验证预检查作业最初检查的关键配置是否仍然有效
  4. 回滚:如果后检查失败,重新配置关键接口

环境准备

在构建网络感知CI/CD流水线之前,请确保已在机器上安装以下组件:

  • Multipass:创建带有预安装Docker的Ubuntu Server VM
  • Cisco路由器镜像:也接受其他供应商的路由器
  • VS Code和Remote Secure Shell扩展:通过SSH访问VM目录

需要具备Linux基础知识,包括创建和更改目录、创建、重命名和复制文件。还需了解SSH主机密钥、如何生成或删除密钥以及Linux操作系统保存位置。此外,需要对Ansible、Docker和CI/CD流水线工具(如GitLab)有基本了解。

基础设施架构设置

创建VM

1
multipass launch --memory 16G --cpus 4 docker

配置SSH访问

获取VM IP地址并配置无密码SSH访问:

1
2
3
multipass shell docker
ip address show ens3  # 我的VM IP地址是192.168.64.7/24
exit

主机配置:

1
2
3
4
5
6
# macOS - 添加ssh配置到主机
sudo tee -a ~/.ssh/config <<EOF
Host docker
  HostName 192.168.64.7
  User ubuntu
EOF

部署路由器到Containerlab

安装Containerlab:

1
bash -c "$(curl -sL https://get.containerlab.dev)"

准备路由器目录并转换镜像:

1
2
3
4
5
6
7
mkdir -p ~/network-aware/router
cd ~/network-aware/router/
git clone https://github.com/hellt/vrnetlab.git
cp x86_64_crb_linux-adventerprisek9-ms.bin vrnetlab/cisco/iol/
mv vrnetlab/cisco/iol/x86_64_crb_linux-adventerprisek9-ms.bin vrnetlab/cisco/iol/cisco_iol-15.7.bin
sudo apt install make
make -C vrnetlab/cisco/iol/ docker-image

创建Containerlab拓扑文件并部署路由器:

1
2
3
4
5
6
7
8
name: cisco-iol
topology:
  nodes:
    R1:
      kind: cisco_iol
      image: vrnetlab/cisco_iol:15.7
  links:
    - endpoints: ["R1:Ethernet0/1", "R1:Ethernet0/2"]
1
2
containerlab deploy --topo cisco-iol.clab.yaml
containerlab inspect

构建和推送Ansible镜像

运行本地Docker Hub注册表:

1
docker run -d -p 5000:5000 --name registry registry:2

构建Ansible镜像并推送到本地Docker Hub:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
FROM ubuntu:latest
RUN apt update && \
    # 安装ansible
    apt install -y software-properties-common && \
    add-apt-repository -y --update ppa:ansible/ansible && \
    apt install -y ansible && \
    # 安装替代ssh实现
    apt install python3-pip -y && \
    pip3 install ansible-pylibssh --break-system-packages && \
    # 安装git
    apt install git -y && \
    apt clean
1
2
docker build -t localhost:5000/ansible:latest .
docker push localhost:5000/ansible:latest

部署GitLab容器

准备GitLab安装目录:

1
2
3
mkdir ~/gitlab-docker
echo export GITLAB_HOME=~/gitlab-docker >> ~/.bashrc
source ~/.bashrc

创建GitLab容器(约需6分钟启动):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
docker run --detach \
  --hostname gitlab \
  --env GITLAB_OMNIBUS_CONFIG="external_url 'http://gitlab'" \
  --publish 443:443 --publish 80:80 \
  --name gitlab \
  --restart always \
  --volume $GITLAB_HOME/config:/etc/gitlab \
  --volume $GITLAB_HOME/logs:/var/log/gitlab \
  --volume $GITLAB_HOME/data:/var/opt/gitlab \
  --shm-size 512m \
  gitlab/gitlab-ce:latest

获取GitLab凭据:

1
2
docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password
# 用户名 = root

配置GitLab

创建GitLab仓库

导航至 Project > New project > Create blank project:

  • 项目名称 = containerlab-project
  • 组 = root
  • 可见性级别 = public

创建Linux运行器

在GitLab UI中导航至 Settings > CI/CD > Runners > Create project runner:

  • 标签 = containerlab
  • 超时 = 600
  • 操作系统 = linux

在VM上注册令牌:

1
2
3
4
RUNNER_TOKEN=<token>
sudo gitlab-runner register \
  --url http://gitlab \
  --token $RUNNER_TOKEN

配置Docker执行器

获取GitLab容器IP地址:

1
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' gitlab

编辑GitLab配置:

1
2
3
[runners.docker]
extra_hosts = ["gitlab:172.17.0.2"]
network_mode = "host"

重启运行器:

1
sudo gitlab-runner restart

设置CI/CD流水线

创建GitLab流水线变量

获取路由器SSH主机密钥:

1
ssh-keyscan 172.20.20.2

在GitLab UI中导航至 Settings > CI/CD > Variable:

  • 键 = KNOWN_HOSTS
  • 值 = <你的主机文件输出>

创建GitLab作业脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
stages:
  - pre_check
  - deploy
  - post_check
  - rollback

image: localhost:5000/ansible:latest

before_script:
  - echo $KNOWN_HOSTS > ~/.ssh/known_hosts

pre_check:
  stage: pre_check
  tags:
    - containerlab
  script:
    - ansible-playbook pre-check.yaml -i inventory.yaml
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'

deploy:
  stage: deploy
  tags:
    - containerlab
  script:
    - ansible-playbook deploy.yaml -i inventory.yaml
  rules:
    - if: $CI_COMMIT_BRANCH == 'main'

post_check:
  stage: post_check
  tags:
    - containerlab
  script:
    - ansible-playbook pre-check.yaml -i inventory.yaml
  rules:
    - if: $CI_COMMIT_BRANCH == 'main'

rollback:
  stage: rollback
  when: on_failure
  tags:
    - containerlab
  script:
    - ansible-playbook rollback.yaml -i inventory.yaml
  rules:
    - if: $CI_COMMIT_BRANCH == 'main'

创建Ansible配置文件

变量文件:

1
2
3
ansible_username: admin
ansible_passwd: admin
ansible_host: 172.20.20.2

清单文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
local:
  hosts:
    clab-cisco-iol-R1:
      ansible_connection: network_cli
      ansible_host: "{{ ansible_host }}"
      ansible_user: "{{ ansible_username }}"
      ansible_password: "{{ ansible_passwd }}"
      ansible_network_os: cisco.ios.ios
      ansible_become: true
      ansible_become_method: enable

创建预检查和后检查剧本

预检查剧本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
- hosts: clab-cisco-iol-R1
  vars_files:
    - vars/credentials.yaml
  tasks:
    - name: Gathers all interfaces
      cisco.ios.ios_facts:
        gather_network_resources:
          - interfaces
      register: int_status

    - name: Print the interface details
      ansible.builtin.debug:
        var: int_status.ansible_facts.ansible_network_resources.interfaces

    - name: Fail if the critical interface is down
      loop: "{{ int_status.ansible_facts.ansible_network_resources.interfaces }}"
      when: item.enabled == false and item.name == "Ethernet0/1"
      ansible.builtin.fail:
        msg: "Interface {{ item.name }} is down!"

创建部署剧本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- hosts: clab-cisco-iol-R1
  vars_files:
    - vars/credentials.yaml
  tasks:
    - name: set interface IP address and mask
      cisco.ios.ios_l3_interfaces:
        config:
          - ipv4:
            - address: 10.1.2.20/24
            name: Ethernet0/2
      register: int_address

    - name: Print the interface IP address
      ansible.builtin.debug:
        var: int_address

    - name: set the interface status up
      cisco.ios.ios_interfaces:
        config:
          - description: critical
            enabled: false  # 关闭接口
            name: Ethernet0/1  # 此接口
      register: int_status

    - name: Print the interface status
      ansible.builtin.debug:
        var: int_status

创建回滚剧本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- hosts: clab-cisco-iol-R1
  vars_files:
    - vars/credentials.yaml
  tasks:
    - name: configure critical interface IP address and mask
      cisco.ios.ios_l3_interfaces:
        config:
          - ipv4:
            - address: 10.1.1.10/24
            name: Ethernet0/1
      register: int_address

    - name: print the interface IP address
      ansible.builtin.debug:
        var: int_address

    - name: enable the critical interface status
      cisco.ios.ios_interfaces:
        config:
          - description: critical
            enabled: true
            name: Ethernet0/1
      register: int_status

    - name: print the interface status
      ansible.builtin.debug:
        var: int_status

观察GitLab仪表板上的作业

推送更改到新分支后,在仓库中导航到该分支并打开合并请求。观察预检查作业、部署作业、后检查作业和回滚作业的执行情况。

替代方案

如果网络工程团队尚未过渡到Python,Ansible仍然可以在流水线中执行一次性实时状态检查。一旦团队采用Python和pyATS,可以逐步将pyATS实现引入流水线,因为它是供应商无关的。

并非每个流水线都需要检查实时网络状态。相反,可以使用Batfish和Suzieq等开源工具通过设备快照执行预检查和后检查。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计