第一次尝试跟随DevOps教程学习时,系统提示我需要注册AWS账户。

我照做了,创建了一个EC2实例,按照教程操作了一个小时,后来却忘记关闭它。一周后,我发现竟然有一笔34美元的账单,而那台机器其实什么都没有运行。

从那以后,我就再没有在别人的基础设施上进行过练习了。

本指南中的所有内容都可以在你的笔记本电脑上完成操作。不需要云账户,不需要信用卡,月底也不会产生任何费用。通过学习本指南,你将能够从零开始搭建多服务器环境,使用Ansible自动配置这些服务器,运行自己编写的网站,甚至可以在故意破坏系统后诊断出出现问题的原因。

而最后这部分内容,才是真正值得学习的环节。

先决条件

在开始学习之前,请确保你满足以下要求:

  • 一台至少拥有8GB内存的笔记本电脑(16GB会更好)

  • 至少20GB的空闲磁盘空间

  • Windows、macOS或Linux操作系统

  • 具有对计算机的管理员权限

  • BIOS/UEFI设置中已启用虚拟化功能

  • 用于初始下载的稳定网络连接

所需的知识和技能水平:

  • 你应该能够熟练使用终端,执行命令、切换目录以及用任何你喜欢的编辑器编辑文本文件。

  • 了解“服务器”“SSH”和“端口”等基本概念会有帮助,但你不一定需要具备Docker、Kubernetes、Vagrant或Ansible的使用经验。本指南会在学习过程中逐步介绍这些工具。

如果你能够按照步骤操作,并且能够在遇到错误时冷静地分析问题,那么你就已经准备好开始学习了。

目录

  1. 什么是DevOps?

  2. 为什么要搭建本地实验环境?

  3. 如何安装Docker

  4. 如何配置Kubernetes

  5. 如何安装kubectl

  6. 如何设置Vagrant环境

  7. 如何安装Ansible

  8. 如何创建你的第一个DevOps项目

  9. 如何故意破坏你的实验环境

  10. 你现在可以做什么

什么是DevOps?

DevOps是一种打破软件开发团队与IT运维团队之间壁垒的做法。

传统上,开发人员编写代码后将其交给运维团队进行部署和维护。这种分工方式会导致延迟、误解以及系统故障。而DevOps则是让这两个团队从一开始就共同协作来完成工作。

本指南中介绍的这些工具各自负责解决这一流程中的特定环节:

  • Docker会将你的应用程序及其所需的所有资源打包成一个可移植的容器,这个容器在任何机器上都能以相同的方式运行。

  • Kubernetes能够大规模管理多个容器,自动处理重启、网络配置以及负载均衡等工作。

  • Vagrant用于创建和管理虚拟机环境,这样你的整个团队就能在完全相同的配置环境中进行工作。

  • Ansible可以自动化地完成多台服务器上的重复性配置任务,而无需为每台服务器单独编写脚本。

为什么要搭建本地实验室?

本地实验室为你提供了一个安全的环境,在这里你可以随意进行实验、解决问题,并从中学习经验,而且这一切都不需要付出任何成本或承担任何风险。

搭建本地实验室后,你能获得以下好处:

  • 零成本。无需支付云服务费用,也不会有意外收费,更不需要使用信用卡。

  • 离线使用。完成初始设置后,你可以在任何地方进行练习,甚至在没有互联网连接的情况下也能使用。

  • 完全可控。

    你可以从操作系统到应用程序这一整套环境进行全方位的管理。

  • 安全可靠的实验环境。

    在这里你可以自由地进行各种测试,而且不会影响到生产环境。

  • 快速获得反馈。

    无需等待云资源启动,所有操作都在你的本地机器上完成。

不过,搭建本地实验室也会带来一些限制。你的笔记本电脑的CPU和RAM容量就是这些限制的因素。因此你无法模拟大规模部署环境,而且像AWS Lambda或S3这样的云原生服务在本地也没有对应的解决方案。但对于学习核心的DevOps工作流程来说,这些限制其实并不重要。

如何搭建Docker环境

Docker是这个实验室的基础。本指南中介绍的其他所有工具要么是在Docker容器内部运行的,要么就是与Docker容器协同工作的。

如何在Windows系统上安装Docker

首先,需要在BIOS中启用虚拟化功能:

  1. 重启电脑,然后进入BIOS/UEFI设置界面。通常在启动过程中按F2、F10、Del或Esc键即可进入该界面。

  2. 找到与虚拟化相关的设置选项,它通常会被标记为“Intel VT-x”、“AMD-V”或“Virtualization Technology”等。

  3. 启用该功能,保存设置后退出BIOS设置界面。

接下来安装Docker Desktop:

  1. Docker官方网站下载Docker Desktop。

  2. 运行安装程序并按照提示进行操作。

  3. 如果系统询问是否启用WSL 2(Windows Subsystem for Linux),请选择启用它。

  4. 重启电脑。

  5. 从“开始”菜单中打开Docker Desktop,等待任务栏中的鲸鱼图标停止动画效果后即可使用该软件。

故障排除:如果Docker无法正常启动,可以以管理员权限在PowerShell中运行以下命令,以确认虚拟化功能是否已经启用:

systeminfo | findstr "Hyper-V Requirements"

所有选项都应该显示“是”。如果没有,請重新检查您的BIOS设置。

如何在Mac上安装Docker

  1. Docker官方网站下载Docker Desktop for Mac。

  2. 打开下载到的〈code>.dmg文件,然后将Docker拖放到“应用程序”文件夹中。

  3. 从“应用程序”文件夹中启动Docker程序。

  4. 系统提示时输入您的密码。

  5. 等待菜单栏中的鲸鱼图标停止动画效果。

如何在Linux上安装Docker

按以下顺序运行这些命令:

# 更新软件包列表
sudo apt-get update

# 安装必备软件
sudo apt-get install apt-transport-https ca-certificates curl software-properties-common

# 添加Docker的官方GPG密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

# 添加Docker仓库
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

# 更新并安装Docker
sudo apt-get update
sudo apt-get install docker-ce

# 启动并启用Docker服务
sudo systemctl start docker
sudo systemctl enable docker

# 将用户添加到docker组中
sudo usermod -aG docker $USER

退出系统后再重新登录,这样用户组的更改才会生效。

如何测试Docker

运行以下命令:

docker run hello-world

如果看到“Hello from Docker!”,说明Docker安装成功且可以正常使用了。

Docker的安装工作已经完成。接下来,您需要安装Kubernetes来大规模管理容器。

如何配置Kubernetes

Kubernetes可用于大规模管理容器。对于本地开发环境来说,有四种选择方案。以下是选择方法:

工具 适合人群 所需内存
Minikube 初学者。配置最简单,内置监控面板 2GB以上
Kind 启动速度更快,非常适合集成在持续集成流程中使用 1GB以上
k3s 资源有限的机器。体积小巧,但功能与真实集群相当 512MB以上
kubeadm 适合了解Kubernetes节点在真实环境中是如何组建集群的 每个节点需2GB以上内存

如果您是初学者,建议使用Minikube。它的配置过程最简单,并且配有可视化监控面板,可以帮助您了解集群内部的运行情况。

如果您的笔记本电脑内存仅为8GB或更低,建议选择k3s。它占用系统资源较少,其运行效果更接近真实集群环境。

只有当您想深入了解Kubernetes节点如何加入集群时,才适合使用kubeadm。不过这个工具需要手动配置的步骤较多,因此不太适合初学者使用。

Minikube可以在你的笔记本电脑上创建一个单节点的Kubernetes集群。

在Windows系统上:

  1. Minikube的GitHub发布页面下载安装程序。

  2. 运行相应的`.exe`安装文件。

  3. 以管理员身份打开命令提示符,然后启动Minikube:

minikube start --driver=docker

在Mac系统上:

brew install minikube
minikube start --driver=docker

在Linux系统上:

curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
chmod +x minikube-linux-amd64
sudo mv minikube-linux-amd64 /usr/local/bin/minikube
minikube start --driver=docker

测试你的集群:

minikube status
minikube dashboard

k3s是Kubernetes的一个轻量级版本,安装时间不到一分钟。它运行起来非常高效,其行为与真正的集群完全一致——而不是某种简化的演示版本。

在Linux系统上(或在Mac系统上通过Multipass工具):

curl -sfL https://get.k3s.io | sh -

这个命令会自动完成k3s的安装并使其在后台运行。请检查它是否已经启动:

sudo k3s kubectl get nodes

你应该能看到一个状态为Ready的节点。

在Mac系统上直接安装k3s是不行的——k3s并不支持macOS原生运行。请先使用Multipass创建一个轻量级的Ubuntu虚拟机,然后再在该虚拟机中执行安装命令。

在Windows系统上,请使用WSL2(基于Ubuntu的虚拟环境),然后在WSL2终端中执行安装命令。

如何安装Kind(Kubernetes在Docker中的实现)

Kind可以在Docker容器内运行一个完整的Kubernetes集群。它的启动速度比Minikube更快,如果你需要同时运行多个集群的话,Kind会非常有用。

# 在Mac或Linux系统上
brew install kind

# 在Windows系统上
choco install kind

创建一个集群:

kind create cluster --name my-local-lab

如何安装kubeadm(用于了解集群初始化过程)

kubeadm是Kubernetes用来初始化节点并让它们加入集群的工具。当你想要了解Kubernetes内部的工作原理时,可以使用这个工具——但它并不适合日常使用。

安装kubeadm至少需要两台机器或虚拟机。其设置过程比上述方法更为复杂。请根据你的操作系统参考官方的安装指南进行操作,然后初始化你的集群。

sudo kubeadm init --pod-network-cidr=10.244.0.0/16

完成初始化后,可以使用 kubeadm 命令加入工作节点。该命令会在输出结果的最后显示相应的指令。

如何安装 kubectl

kubectl 是一种命令行工具,用于与任何 Kubernetes 集群进行交互。

在 Windows 上:

Kubernetes 官网 下载 kubectl.exe,并将其放在你的 PATH 所包含的目录中。或者使用 Chocolatey 进行安装:

choco install kubernetes-cli

在 Mac 上:

brew install kubectl

在 Linux 上:

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/kubectl

测试一下:

kubectl get pods --all-namespaces

在一个全新的集群中,你会看到运行在 kube-system 名称空间中的系统节点——比如 corednsstorage-provisioner。这就是预期的输出结果。这说明你的集群已经启动成功,kubectl 可以正常与之交互了。

Kubernetes 已经运行起来了。接下来要介绍的是 Vagrant。但在那之前,有一个人们需要了解的重要区别。

Docker 与 Vagrant — 它们不是同一种东西

Docker 创建的是容器:这些轻量级的进程会共享你的操作系统的内核。而 Vagrant 则创建的是完整的虚拟机:这些独立的计算机会在你的笔记本电脑上运行自己的操作系统。

容器体积小、运行速度快;而虚拟机的体积较大,但它们的行为与真正的服务器完全一样。在这个实验中,你会出于不同的原因使用这两种工具。

如何设置 Vagrant

Vagrant 可以帮助你创建并管理可复制的虚拟机环境。对于在单台笔记本电脑上模拟多服务器环境来说,它是非常理想的工具。

如何在 Windows 上安装 Vagrant

  1. 下载并按照默认设置安装 VirtualBox

  2. 下载并安装 Vagrant

  3. 如果系统提示,重新启动你的电脑。

注意:在 Windows 上,VirtualBox 和 Hyper-V 不能同时运行。请检查 Hyper-V 是否处于激活状态:

systeminfo | findstr "Hyper-V"

如果 Hyper-V 已经被启用,你可以选择两种方式:要么切换到使用 Hyper-V 的 Vagrant 提供程序,要么通过以下命令禁用 Hyper-V:

Disable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All

禁用后请重新启动电脑。

如何在 Mac 和 Linux 上安装 Vagrant

在 Mac 上:

  1. 下载并安装 VirtualBox

  2. 安装完成后,打开 系统偏好设置 > 安全与隐私 > 通用。你会看到一条提示信息,说明 Oracle 的系统软件被阻止了。点击 允许,然后重新启动你的 Mac。如果不执行这一步,VirtualBox 将无法运行。

  3. 下载并安装 Vagrant

对于使用 Apple Silicon (M1/M2/M3) 处理器的 Mac: VirtualBox 对 Apple Silicon 的支持仍然有限。如果你使用的是 M 系列的 Mac,建议改用 UTM 作为虚拟机管理工具,或者使用在 Apple Silicon 上能够正常运行的 Multipass。

在 Linux 上:

  1. 下载并安装 VirtualBox

  2. 下载并安装 Vagrant

确认这两个工具都已经安装完成:

vboxmanage --version
vagrant --version

如何创建你的第一个 Vagrant 环境

为你的项目创建一个新目录,在其中创建一个名为 Vagrantfile 的文件,内容如下:

Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/focal64"

  # 在虚拟机之间创建一个私有网络
  config.vm.network "private_network", type: "dhcp"

  # 将笔记本电脑上的 8080 端口映射到虚拟机上的 80 端口
  config.vm.network "forwarded_port", guest: 80, host: 8080

  # 在虚拟机启动时安装 Nginx
  config.vm.provision "shell", inline: <<-SHELL
    apt-get update
    apt-get install -y nginx
    echo "Hello from Vagrant!" > /var/www/html/index.html
  SHELL
end

启动虚拟机:

vagrant up

显示 VB 服务器和终端安装过程的截图

在浏览器中访问 http://localhost:8080,你应该会看到 “Hello from Vagrant!” 这一提示信息。

浏览器中显示“Hello from Vagrant!”的截图

在 Windows 上解决 SSH 连接问题

如果 vagrant ssh 命令执行失败,可以尝试以下方法:

vagrant ssh -- -v

或者手动连接:

ssh -i .vagrant/machines/default/virtualbox/private_key vagrant@127.0.0.1 -p 2222

如何在没有互联网的情况下创建本地Vagrant虚拟机

注意:大多数读者可以跳过这一部分。只有在你希望在完成初始设置后完全脱离网络环境进行操作时,才需要执行这些步骤。

  1. 下载Ubuntu 20.04 LTS,并将生成的.iso文件保存到本地。

  2. 打开VirtualBox,创建一个新的虚拟机:将其命名为ubuntu-devops,类型设为Linux,版本选择Ubuntu(64位)。

  3. 为该虚拟机分配2048MB的RAM内存和20GB的VDI磁盘空间。

  4. 将下载的.iso文件通过“存储”>“光驱”选项添加到虚拟机中。

  5. 启动虚拟机,完成Ubuntu系统的安装过程。

  6. 安装完成后,关闭虚拟机,然后运行以下命令:

VBoxManage list vms
vagrant package --base "ubuntu-devops" --output ubuntu2004.box
vagrant box add ubuntu2004 ubuntu2004.box

现在你已经拥有一个可以在没有互联网的情况下使用的本地虚拟机了。

接下来你可以创建更多的虚拟机,而Ansible则可以自动完成这些虚拟机中的各种配置任务。

如何安装Ansible

Ansible能够自动化地配置多台服务器并安装各类软件。你无需手动登录到十台机器上执行相同的命令,只需编写一次脚本,Ansible就会自动完成后续的所有操作。

如何在Windows系统上安装Ansible

Ansible在Windows系统中无法直接运行,你需要通过WSL(Windows Subsystem for Linux)来使用它。

  1. 以管理员身份打开PowerShell,然后启用WSL:
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
  1. 重启你的电脑。

  2. 通过Microsoft Store安装Ubuntu系统。
  3. 进入Ubuntu系统后,安装Ansible:
    1. sudo apt update
      sudo apt install software-properties-common
      sudo apt-add-repository --yes --update ppa:ansible/ansible
      sudo apt install ansible
      

      如何在Mac系统上安装Ansible

      brew install ansible
      

      如何在Linux系统上安装Ansible

      # 对于Ubuntu/Debian系统
      sudo apt update
      sudo apt install software-properties-common
      sudo apt-add-repository --yes --update ppa:ansible/ansible
      sudo apt install ansible
      
      # 对于Red Hat/CentOS系统
      sudo yum install ansible
      

      如何测试Ansible

      在当前目录下创建一个名为hosts的文件:

      [local]
      localhost ansible_connection=local
      

      在同一目录下再创建一个名为playbook.yml的文件:

      ---
      - name: 测试剧本
        hosts: local
        tasks:
          - name: 输出一条消息
            debug:
              msg: "Ansible正在运行中!"
      

      运行该剧本,使用`-i`选项指定`local`主机文件:

      ansible-playbook -i hosts playbook.yml
      

      你应该会在输出结果中看到“Ansible正在运行中!”这条消息。

      显示Ansible剧本安装完成后的终端界面

      好了,所有的工具都已经安装完成了。现在你可以将它们结合起来,来创建一些实际的项目。

      如何构建你的第一个DevOps项目

      你可以在这个仓库中找到本次实验的所有代码:https://github.com/Osomudeya/homelab-demo-article

      现在,你需要把这些工具整合到一个项目中。每个工具都会执行它自己的功能,没有任何强制性的要求。

      在开始之前,请为这个项目创建一个全新的目录。不要在之前用于测试Vagrant的目录中运行这个项目,因为这里的Vagrantfile与之前的不同,可能会引发冲突。

      你将构建一个由两台虚拟机组成的环境:其中一台机器会在Docker容器中运行你自己编写的网页,另一台机器则会运行MariaDB数据库。Vagrant负责创建这些虚拟机,而Ansible则负责配置它们。最后你看到的页面就是你自己制作的。

      步骤1:创建项目目录

      mkdir devops-lab-project && cd devops-lab-project
      

      步骤2:编写网站内容

      在项目目录中创建一个名为`index.html`的文件。在这个文件中写入你想要显示的内容——最终这些内容会出现在你的浏览器中:

      <!DOCTYPE html>
      <html>
        <title>我的DevOps实验室</head>
        
          

      我的DevOps实验室

      由Vagrant搭建环境,Ansible进行配置,Docker提供服务。

      在笔记本电脑上完成开发,无需云账户。

      你可以根据自己的喜好修改这些内容。这就是属于你的网页了。

      步骤3:编写Vagrantfile

      在同一个目录中创建一个名为`Vagrantfile`的文件:

      Vagrant.configure("2") do |config|
        config.vm.box = "ubuntu/focal64"
      
        config.vm.define "web" do |web|
          web.vm.network "private_network", ip: "192.168.33.10"
          web.vm.network "forwarded_port", guest: 80, host: 8080
        end
      
        config.vm.define "db" do |db|
          db.vm.network "private_network", ip: "192.168.33.11"
        end
      end
      

      步骤4:启动虚拟机

      vagrant up

      首次运行时,系统会下载大小约为500MB的ubuntu/focal64镜像文件。

      终端中显示的VirtualBox安装过程截图

      根据你的网络连接速度,这个过程可能需要10到30分钟。后续的运行会快很多,因为镜像文件已经存储在本地了。

      VB管理器中显示的2台正在运行的VirtualBox服务器截图

      步骤5:创建Ansible清单文件

      在同一目录下创建一个名为inventory的文件:

      [webservers]
      192.168.33.10 ansible_user=vagrant ansible_ssh_private_key_file=.vagrant/machines/web/virtualbox/private_key
      
      [dbservers]
      192.168.33.11 ansible_user=vagrant ansible_ssh_private_key_file=.vagrant/machines/db/virtualbox/private_key
      

      Ansible会使用Vagrant生成的私钥文件,因此它可以以vagrant用户的身份进行SSH连接。在这个实验环境中,ansible.cfg文件中已经禁用了对主机密钥的验证功能(具体步骤见下一步),而不是在清单文件中进行设置。

      步骤6:创建Ansible配置文件

      在运行Playbook之前,首先在同一目录下创建一个名为ansible.cfg的文件:

      [defaults]
      inventory = inventory
      host_key_checking = False
      

      其中,“inventory”这一设置告诉Ansible默认使用当前文件夹中的清单文件;“host_key_checking = False”则指示Ansible在连接Vagrant虚拟机时不要验证主机密钥。如果不设置这个选项,第一次连接时会因为虚拟机的密钥尚未被添加到known_hosts文件中而出现错误。

      这些配置仅适用于本地实验环境,请勿在生产系统中使用“host_key_checking = False”这一设置。

      步骤7:创建Ansible Playbook脚本

      创建一个名为playbook.yml的文件:

      ---
      - name: 配置Web服务器
        hosts: webservers
        become: yes
        tasks:
      
          - name: 安装Docker
            apt:
              name: docker.io
              state: present
              update_cache: yes
      
          - name: 启动Docker服务
            service:
              name: docker
              state: started
              enabled: yes
      
          - name: 创建用于存放网站内容的目录
            file:
              path: /var/www/html
              state: directory
              mode: '0755'
      
          - name: 将index.html文件从笔记本电脑复制到虚拟机中
            copy:
              src: index.html
              dest: /var/www/html/index.html
      
          - name: 将该文件挂载到Nginx容器中,以便服务器能够提供网站内容
            -v flag用于将虚拟机中的/var/www/html目录与容器内的/usr/share/nginx/html目录进行关联
            - name: 运行Nginx服务器
            shell: |
              docker rm -f webapp 2>/dev/null || true
              docker run -d --name webapp --restart always -p 80:80 \
                -v /var/www/html:/usr/share/nginx/html:ro nginx
      
      - name: 配置数据库服务器
        hosts: dbservers
        become: yes
        tasks:
      
          # .deb文件的哈希值不匹配通常是由于镜像源过时、代理设置问题或NAT网络导致的。在实验环境中,更新索引文件并将Pipeline-Depth设置为0通常可以解决这个问题。
          - name: 禁用apt的HTTP管道传输功能
            copy:
              dest: /etc/apt/apt.conf.d/99disable-pipelining
              content: 'Acquire::http::Pipeline-Depth "0";'
              mode: "0644"
      
          - name: 清除apt包索引缓存
            shell: apt-get clean && rm -rf /var/lib/apt/lists/* /var/lib/apt/lists/auxfiles/*
            changed_when: true
      
          - name: 重置后更新apt缓存
            apt:
              update_cache: yes
      
          - name: 安装MariaDB数据库
            apt:
              name: mariadb-server
              state: present
              update_cache: no
      
          - name: 启动MariaDB服务
            service:
              name: mariadb
              state: started
              enabled: yes
      

      有两条内容需要注意:

      • src: index.html — Ansible会在这份剧本所在的同一目录中查找这个文件。也就是你在第2步中创建的文件。

      • -v /var/www/html:/usr/share/nginx/html:ro — 这条命令会将虚拟机中的该目录挂载到Nginx容器中。:ro表示只读模式,Nginx会从该文件夹中提供内容。

      步骤8:运行剧本

      ansible-playbook -i inventory playbook.yml

      当Ansible通过SSH连接到每台虚拟机并进行配置时,你会看到逐项的执行结果。每个任务旁边显示的绿色ok或黄色changed表示操作成功;红色fatal则表示出现了故障。

      终端截图:每个任务旁边显示绿色“ok”或黄色“changed”,表示操作成功;红色“fatal”表示出现故障。
      剧本运行完成后的终端截图

      步骤9:验证配置结果

      在浏览器中访问http://localhost:8080,你应该能看到在第2步中创建的页面——这个页面实际上是由运行在Vagrant虚拟机上的Docker容器提供的,而所有配置都是通过Ansible自动完成的。

      如果能够看到这个页面,那就说明这个实验环境中的所有工具都在正常工作。

      浏览器显示的页面内容:标题为“我的DevOps实验室”,文字提示“由Vagrant搭建,Ansible进行配置,Docker提供服务。” height="400" loading="lazy" src="https://cdn.hashnode.com/uploads/covers/698d563262d4ce66226a844a/0d3d897b-3f51-46fb-b548-832cc5ec3272.png" style="display:block;margin:0 auto" width="600"/></p>
<h3 id="heading-step-9-clean-up-optional">步骤9:清理环境(可选)</h3>
<p>当你完成所有操作后,可以执行以下命令:</p>
<pre><code class="language-bash">vagrant destroy -f</code></pre>
<p>这条命令会关闭并删除这两台虚拟机。不过你的<code>Vagrantfile</code>、<code>inventory</code>、<code>playbook.yml</code>以及<code>index.html</code>文件仍然会保留在磁盘上——随时可以通过运行<code>vagrant up</code>再执行<code>ansible-playbook -i inventory playbook.yml</code>来恢复之前的环境。</p>
<p>现在你的实验环境已经准备就绪,接下来就可以好好利用它了吧。</p>
<h2 id="heading-how-to-break-your-lab-on-purpose">如何故意破坏你的实验环境</h2>
<p>按照前面的步骤操作,你已经建立了一个可以正常运行的实验环境。而通过故意破坏某些设置,你才能真正了解这些组件的工作原理。</p>
<p>下面列出了五项可以尝试破坏的内容,以及破坏后应该观察哪些现象。</p>
<h3 id="heading-break-1-crash-the-main-process-inside-the-container-and-watch-it-come-back">破坏1:让容器内的主进程崩溃,然后观察它如何恢复</h3>
<p>这样做恰恰证明了:容器内部的部分进程确实可能会出现故障(比如程序崩溃或内存不足),但由于设置了<code>--restart always</code>选项,Docker能够自动重启容器,因此你的网站依然可以正常运行,而无需重新执行Ansible脚本。</p>
<p>在执行了<code>vagrant ssh web</code>命令之后,后续所有的<code>docker</code>命令都会在<strong>Web虚拟机上</strong>被执行。所以请在笔记本电脑上打开浏览器,访问<a href="http://localhost:8080"><code>http://localhost:8080</code></a>(Vagrant会将主机上的端口映射到虚拟机的80端口上)。</p>
<h4 id="heading-troubleshooting-if-your-lab-isnt-ready">故障排除:如果你的实验室环境尚未准备好</h4>
<p>在主机(即你的笔记本电脑)上的项目文件夹中操作时——除非有特别说明需要在虚拟机上执行相应命令,否则请按照以下步骤操作:</p>
<ul>
<li>
<p>如果你已经执行了<code>vagrant destroy -f</code>命令,请先运行<code>vagrant up</code>,然后再执行<code>ansible-playbook -i inventory playbook.yml</code>。</p>
</li>
<li><p如果通过<code>docker ps</code>查看到<code>webapp</code>容器存在,但其状态显示为“Exited”,那么在Web虚拟机上运行<code>sudo docker start webapp</code>,然后再重新执行<code>sudo docker ps</code>。</p>
</li>
<li><p如果通过<code>docker ps -a</code>仍然看不到<code>webapp</code>容器,那么请在主机上再次执行<code>ansible-playbook -i inventory playbook.yml</code>。</p>
</li>
</ul>
<p>如果Ansible脚本已经成功应用,且<code>webapp</>容器处于运行状态,请跳过这一部分,直接开始下面的“正常操作步骤”。不过请务必先执行SSH连接命令和<code>docker ps</code>命令,因为在运行<code>docker exec</code>之前,你需要先确认虚拟机的状态。</p>
<h4 id="heading-steps-happy-path">正常操作步骤</h4>
<ol>
<li>首先通过SSH连接到Web虚拟机:</li>
</ol>
<pre><code class="language-plaintext">vagrant ssh web</code></pre>
<ol>
<li>
<p>确认<code>webapp</code>容器是否处于运行状态:</p>
<pre><code class="language-plaintext">sudo docker ps</code></pre>
</li>
<li>
<p>现在故意“破坏”这个容器:从虚拟机内部终止容器的主进程(PID为1)。这样容器就会像普通程序崩溃一样被关闭,这与在主机上使用<code>docker stop</code>命令的效果是不同的。</p>
</li>
</ol>
<pre><code class="language-bash">sudo docker exec webapp sh -c

      执行这条命令后,会有5秒钟的时间让你有时间切换到浏览器。立即打开或刷新http://localhost:8080页面。此时你可能会看到一条短暂的错误信息,或者看到一个空白页面,因为此时还没有进程在80端口上监听请求。

      当Nginx容器进程被终止后,浏览器显示“ERR_CONNECTION_RESET”错误

      1. 观察Docker如何重启该容器:
      watch sudo docker ps -a

      终端窗口中显示的docker ps输出,其中webapp容器的状态在自动重启后10秒内仍为“Up”

      几秒钟后,你应该会看到“Exited (137)”状态再次变为“Up”。(按Ctrl+C即可退出watch命令。)

      5. 刷新浏览器页面。你应该会看到与之前相同的HTML内容,因为这些文件实际上存储在虚拟机的/var/www/html目录下,并且被挂载到了容器中;重新启动服务器只是终止了Nginx进程,并没有改变这些文件的所在位置。

      为什么在这个演示中不要在主机上使用docker stopdocker kill命令呢?

      这些命令是通过Docker的API来执行的。在许多环境中(包括最新的Docker版本),Docker会将这些操作视为用户主动终止了容器(hasBeenManuallyStopped),因此即使使用了--restart always选项,容器也不会自动重新启动,直到你再次使用docker start命令才行。

      在容器内部直接终止PID为1的进程,其实相当于发生了内部故障;因此,在Playbook中设置的重启策略才会真正影响到容器的行为。

      Kubernetes的类比:对于Kubernetes来说,如果某个Pod中的容器退出了,kubelet会自动尝试重新启动这些容器;而如果你直接删除了一个Pod,那么该Pod是不会自动恢复的。

      需要观察的内容(三项检查):

      1. 退出代码:执行kill 1命令后,使用docker ps -a查看容器的退出代码。如果退出代码为137,说明主进程是被信号终止的,这证明了容器确实已经关闭了,而不是因为你在主机上执行了docker stop命令。

      2. 重启延迟与浏览器显示的结果:观察在docker ps -a中,“Exited”状态变为“Up”状态之间间隔了多少秒——这个间隔实际上就是Docker在执行--restart always选项所花费的时间。不过,浏览器显示的结果可能与此不同:浏览器只会判断虚拟机上的80端口是否在接收连接请求,因此即使在Docker即将重新启动容器的过程中,浏览器也可能显示错误信息或空白页面。

      3. 恢复后的内容:当容器的状态再次变为“Up”后,刷新页面。你应该会看到与之前相同的HTML内容,这说明你的网站文件实际上是存储在虚拟机上的磁盘中的(通过-v选项挂载到了容器中),而不是存储在会在容器进程重启时被删除的文件中。实际上被替换的是容器内的进程,而主机路径下的index.html文件并没有受到影响。

      实验步骤2:导致容器名称冲突

      在同一个Docker守护进程中(例如你的Web虚拟机上),容器的名称必须是唯一的。两个正在运行或已经停止的容器不能使用相同的名称。那些总是使用docker run --name webapp命令来创建容器却不先清理现有容器的脚本和Playbook,经常会遇到这种名称冲突的问题;不过及时发现并避免这类问题,在实际工作中可以节省很多时间。

      开始之前需要注意:Ansible已经创建了一个名为webapp的容器。
      请确保你仍然处于Web虚拟机中(例如通过vagrant ssh web进入该虚拟机),这样下面的命令才会在正确的容器上执行。

      现在,试着创建第二个容器,并将其命名为webapp。这里特意使用了普通的nginx镜像——关键在于名称冲突,而不是你的网站所使用的端口或卷挂载设置。

      sudo docker run -d --name webapp nginx
      

      实际上,Docker并不会创建第二个容器,而是会立即返回错误信息。你原来的webapp容器也不会发生任何变化。

      这是因为名称webapp已经被现有的容器注册使用了(错误信息中会显示那个容器的ID)。在旧的容器被删除或重命名之前,Docker不会允许使用这个名称。

      错误的示例信息如下(你的容器ID可能会有所不同):

      docker: Error response from daemon: Conflict. The container name "/webapp" is already in use by container "2e48b81a311c4b71cdc1e25e0df75a22296845c7eb53aab82f9ae739fb6410ec". You have to remove (or rename) that container to be able to reuse that name.
      See 'docker run --help'.
      

      容器名称冲突的终端错误截图

      为了解决这个问题,首先需要释放这个名称,然后再按照之前的方法创建webapp容器(配置端口80、挂载HTML文件、设置重启策略):

      sudo docker rm -f webapp
      sudo docker run -d --name webapp --restart always -p 80:80 \
        -v /var/www/html:/usr/share/nginx/html:ro nginx
      

      完成这些操作后,你的网站应该能够像之前一样正常运行了(可以在笔记本电脑上访问http://localhost:8080查看效果)。

      需要注意的事项:

      请仔细阅读Docker给出的错误信息。你会看到名称/webapp已经被某个容器占用,错误信息中还会提供那个容器的ID。在生产环境中,这意味着“已经有其他容器使用了这个名称”,因此你需要删除或重命名该容器,然后再尝试运行docker run命令。

      测试环节3:让Ansible无法连接到虚拟机

      Ansible会将“无法连接”与“已连接但任务失败”这两种情况区分开来。前者表示无法到达目标虚拟机,后者则表示任务执行失败。了解具体是哪种情况,就能判断是需要修复网络配置/SSH设置,还是检查Playbook文件、依赖包或权限问题。

      在笔记本电脑上的项目文件夹中,编辑inventory文件,将Web服务器的地址从192.168.33.10改为一个没有任何虚拟机正在使用的IP地址,例如192.168.33.99。保存文件后,重新尝试运行Ansible任务。

      [webservers]
      192.168.33.99 ansible_user=vagrant ansible_ssh_private_key_file=.vagrant/machines/web/virtualbox/private_key
      

      您需要在主机上同一个项目文件夹中运行以下命令:

      ansible-playbook -i inventory playbook.yml

      执行此命令后,Ansible会尝试通过SSH连接到192.168.33.99。但由于您的实验网络中没有任何设备响应这个地址(或者SSH连接始终失败),因此Ansible不会在该Web服务器上执行任何任务,并且会显示“UNREACHABLE”错误提示:

      fatal: [192.168.33.99]: UNREACHABLE! => {"msg": "Failed to connect to the host via ssh"

      出现这种错误是很正常的,因为当IP地址错误、虚拟机未启动、防火墙阻止了端口22的通信,或者网络配置有误时,也会出现类似的提示。归根结底,问题都在于无法建立有效的SSH连接

      现在需要恢复正常状态:将192.168.33.10重新添加到inventory文件中,然后再次运行ansible-playbook -i inventory playbook.yml。如果实验环境正常运行,这个命令应该能够成功连接到虚拟机并完成执行。

      “UNREACHABLE”与“FAILED”——如何区分:

      • 如果Ansible显示“UNREACHABLE”,说明它根本无法建立与该主机的SSH连接,因此也不会在该主机上执行任何任务。在继续调试playbook逻辑之前,请先检查IP地址是否正确、虚拟机是否正在运行、防火墙设置是否合理,以及密钥文件路径是否正确。

      • 如果Ansible显示“FAILED”,说明SSH连接已经建立,但某个任务在执行过程中出现了错误。请仔细阅读该任务的输出信息,以确定具体的故障原因(例如包名称、权限问题或语法错误),而不是首先考虑网络问题。

      在后续的调试过程中,请注意Ansible显示的关键字:UNREACHABLE表示无法建立连接,而FAILED则表示任务执行失败,具体原因需要根据任务输出信息来确定。

      练习4:为虚拟机磁盘添加空间

      数据库和其他服务需要足够的磁盘空间来存储日志文件、临时文件以及数据。当文件系统空间已满或接近满时,这些服务可能会无法启动或在运行过程中出现故障。在这个练习中,我们将采用与在实际服务器上相同的诊断方法:首先检查磁盘空间使用情况,然后查看与该服务相关的systemd日志和journal日志。

      以下所有命令都需要在vagrant ssh db之后,在数据库虚拟机上执行。MariaDB已经通过之前的playbook脚本被安装在该虚拟机上。

      操作步骤:

      1. 首先在数据库虚拟机上打开shell终端:

        vagrant ssh db
      2. 创建一个大小为1GB的文件,其中所有内容都是0,以此模拟占用大量磁盘空间的情况:

        sudo dd if=/dev/zero of=/tmp/bigfile bs=1M count=1024
        
        df -h

        使用df -h命令查看根文件系统的空间使用情况。如果您的Vagrant虚拟机磁盘容量足够大,那么创建1GB的文件可能只会使空间利用率略有提升。不过,即使如此,这个操作仍然有助于您了解磁盘空间的使用情况。为了获得更明显的效果,您可以在仅限实验环境中尝试增加count的值,但切勿在生产环境中故意占用大量磁盘空间。

      3. 让systemd重新启动MariaDB服务,并查看其运行状态:

        sudo systemctl restart mariadb
        sudo systemctl status mariadb

        如果磁盘空间已经严重不足,重新启动服务可能会导致失败,或者系统会显示该服务处于“failed”或“not running”状态。

      4. 如果发现任何异常情况,请查看MariaDB服务的日志文件:

        sudo journalctl -u mariadb --no-pager | tail -20

        错误信息通常会指出磁盘空间不足、文件系统为只读模式,或者InnoDB数据库无法写入数据等问题。

      5. 最后,请清理临时文件,确保虚拟机能够继续正常使用:

        sudo rm /tmp/bigfile

        可选地,再次运行sudo systemctl restart mariadb命令,确认服务已经成功重新启动并处于运行状态。

      需要注意的事项:

      • 首先,你应该使用 `df -h` 命令来确认文件系统是否真的处于资源不足的状态。这样,在发现磁盘空间充足时,就可以避免将问题归咎于数据库。

      • 你需要运行 `systemctl status mariadb` 来查看 systemd 是否认为 MariaDB 服务处于活动状态、已经失败,或者处于不稳定的运行状态。

      • 当服务状态不佳时,应运行 `journalctl -u mariadb`,以便将故障原因与 MariaDB 或内核的具体错误信息联系起来。在生产环境中,也是按照这个顺序来排查问题的。

      练习 5:在资源不足的情况下运行 Minikube

      Kubernetes 会将 Pod 分配到拥有足够 CPU 和内存资源的节点上。如果你请求的资源超出了集群的承载能力,一些 Pod 就会处于 待启动状态,而 事件日志 会说明具体原因(例如“CPU 资源不足”)。这与那些启动后立刻崩溃的 Pod 是不同的。

      要进行这个练习,你需要在自己的笔记本电脑上安装一个本地 Kubernetes 集群(本指南中我们使用的是 Minikube),并且确保你的电脑上安装了 `kubectl` 工具。这个练习不使用 Vagrant 虚拟机。如果你还没有安装 Minikube,请先完成“如何设置 Kubernetes”这一部分的内容,或者等到安装完成后再继续进行这个练习。

      你需要在自己的 Mac、Linux 或 Windows 终端 上执行这些操作,而不是在 `vagrant ssh》环境中。如果你仍然处于虚拟机内部,请输入 `exit` 退出虚拟机环境,回到主机终端。

      具体操作步骤:

      1. 首先检查 Minikube 的运行状态:

        minikube status
        

        如果它已经停止运行,就重新启动它(Docker 驱动程序的配置方法与前面的章节相同):

        minikube start --driver=docker
        
      2. 创建一个包含大量 Replicas 的 Deployment,这样单个 Minikube 节点就无法同时运行所有这些 Pod:

        kubectl create deployment stress --image=nginx --replicas=20
        
        # 观察 Pod 的启动过程
        kubectl get pods -w
        

        观察一段时间后,按 Ctrl+C 结束操作。有些 Pod 可能会处于 待启动状态,而另一些则已经处于 运行中

      3. 从 `kubectl get pods` 的输出结果中选取一个处于 待启动状态 的 Pod,然后查看其详细信息:

        kubectl describe pod 
        

        在“Events”部分中,查找 “FailedScheduling” 这一提示,以及类似以下的错误信息:

        Warning  FailedScheduling  0/1 nodes are available: 1 Insufficient cpu.
        

        根据你的机器配置,也可能出现“内存不足”的错误信息。

      4. 为了解决这个问题,你需要减少 Pod 的数量,让集群有足够的能力来运行这些 Pod:

        kubectl scale deployment stress --replicas=2
        

        完成操作后,你可以使用 `kubectl delete deployment stress` 命令来删除这个 Deployment。

      需要注意的事项:

      • 你应该会看到,那些处于“待调度”状态的Pod在系统资源释放之前不会被安排到任何节点上执行。这通常是因为 해당节点没有足够的CPU或内存来运行这些Pod。

      • 你可以运行命令`kubectl describe pod `,然后滚动到“Events”部分。如果出现“CPU不足”或“内存不足”这样的提示,说明集群已经没有可调度资源了,并不意味着容器镜像本身有问题。

      • 与之相反的是,那些成功进入“运行”状态却又立即陷入“CrashLoopBackOff”状态的Pod,通常表示容器内的进程不断异常退出。这种情况属于应用程序或配置问题,而不是因为“没有可运行的节点”导致的。

      你现在可以做什么

      通过这个教程,你不仅安装了相关工具,还实际使用了它们。

      现在你可以仅用一个文件来启动两台服务器。你可以编写脚本,自动完成软件的安装和容器的部署,而无需手动操作任何一台机器。

      你可以在运行在Vagrant虚拟机上的Docker容器中展示自己编写的网页内容,而且只需一条命令就能恢复所有配置到初始状态。

      同时,你也亲自遇到了一些问题:了解了容器冲突的具体表现、当Ansible无法连接目标机器时会显示什么错误信息、磁盘压力会对正在运行的服务产生什么影响,以及Kubernetes调度器在资源不足时会发出怎样的提示。这些错误信息对你来说已经不再陌生了。

      这就是那些仅仅了解DevOps理论与那些真正实践过它的人之间的区别。

      以下是四个可以在同一实验环境中进一步探索的免费项目:

      • DevOps Home-Lab 2026 — 使用Docker Compose、Kubernetes、Prometheus/Grafana进行监控,结合ArgoCD实现GitOps管理,同时利用Cloudflare实现全球服务部署。

      • KubeLab — 模拟真实的Kubernetes故障场景,观察集群在遇到Pod崩溃、OOMKill、节点资源耗尽等问题时的响应机制,并通过实时数据监控这些过程。

      • K8s Secrets Lab — 建立从AWS Secrets Manager到你的Kubernetes集群的完整秘密管理流程,包括密码轮换机制和IRSA加密技术。

      • DevOps Troubleshooting Toolkit — 提供针对Linux系统、容器、Kubernetes、云平台、数据库以及可观测性方面的结构化调试指南,其中还包含了可用于解决实际问题的命令模板。

      所有这些项目都是免费且开源的:github.com/Osomudeya/List-Of-DevOps-Projects

      如果你想更深入地学习,还可以在“搭建属于自己的DevOps实验室”页面找到关于Terraform、Ansible、监控系统、CI/CD流程以及模拟的三台服务器生产环境的详细内容。

Comments are closed.