从零上手:在Linux虚拟机里点亮你的第一个容器

你是否也曾被“环境依赖”问题折磨?代码在本地运行良好,一到服务器就各种报错。今天,我将带你在RHEL9虚拟机上,通过实际动手操作,点亮你的第一个容器,亲身体验这项革命性技术如何解决这一痛点。

为什么需要容器技术?

在传统部署方式中,应用直接运行在物理机或虚拟机上,需要与底层操作系统共享库、配置文件等环境依赖。这就导致了经典的“在我电脑上能运行”的问题。

容器技术通过操作系统级别的虚拟化,将应用及其所有依赖打包在一个独立的运行时环境中。想象一下集装箱运输:

  • 标准化:无论在何处,集装箱内的货物都受到同样保护

  • 隔离性:一个集装箱的问题不会影响其他集装箱

  • 便携性:可在任何支持集装箱的船舶、卡车间运输

容器正是软件世界的“集装箱”。

环境准备

1. 安装RHEL9虚拟机

我们首先需要准备一个干净的Linux环境。如果你已经有Linux环境,可以跳过这一步。

我推荐使用VirtualBox或VMware创建虚拟机:

  • 分配至少2GB内存、20GB硬盘空间

  • 安装介质选择RHEL9 ISO文件

  • 在安装过程中,确保勾选“Server with GUI”或“Minimal Install”并开启网络连接

2. 系统基础配置

安装完成后,登录系统并执行以下基础配置:

bash

# 切换到root用户(或使用sudo)
sudo -i

# 更新系统
dnf update -y

# 安装必要的工具
dnf install -y vim wget curl net-tools

# 设置主机名
hostnamectl set-hostname container-lab

3. 配置SSH远程连接(可选但推荐)

为方便操作,可以配置SSH服务:

bash

# 启动SSH服务
systemctl enable --now sshd

# 查看IP地址
ip addr show

# 配置防火墙允许SSH
firewall-cmd --permanent --add-service=ssh
firewall-cmd --reload

现在可以从宿主机通过SSH连接到虚拟机,复制粘贴命令会更方便。

安装容器运行时

在RHEL9中,我们使用Podman作为容器运行时。Podman是Red Hat推出的Daemonless容器引擎,与Docker命令行高度兼容但无需守护进程。

1. 启用容器模块

bash

# 启用容器模块
dnf module enable -y container-tools

# 安装Podman
dnf install -y podman

# 验证安装
podman --version

你应该看到类似 podman version 4.x.x 的输出,表示安装成功。

2. 配置非root用户运行容器(重要安全实践)

默认情况下,Podman需要root权限运行容器。我们可以配置非root用户的容器环境:

bash

# 为普通用户配置子UID和子GID(假设你的用户名是student)
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 student

# 验证配置
grep student /etc/subuid
grep student /etc/subgid

注意:配置完成后需要重新登录才能生效。

运行第一个容器

现在让我们开始容器探险之旅!

1. 拉取第一个镜像

bash

# 搜索可用的容器镜像
podman search nginx

# 拉取Nginx官方镜像
podman pull docker.io/library/nginx:alpine

# 查看已下载的镜像
podman images

alpine 标签表示这是基于Alpine Linux的轻量级镜像,体积小,非常适合学习使用。

2. 运行并探索容器

bash

# 以交互模式运行容器
podman run -it --name my-first-container docker.io/library/nginx:alpine /bin/sh

# 你现在已经进入容器内部!
# 查看容器内的进程
ps aux

# 查看容器内的文件系统
ls -la /

# 查看操作系统信息
cat /etc/os-release

# 退出容器(容器会停止运行)
exit

3. 以守护进程模式运行容器

bash

# 以后台模式运行Nginx容器
podman run -d --name web-server -p 8080:80 docker.io/library/nginx:alpine

# 查看运行中的容器
podman ps

# 查看容器日志
podman logs web-server

# 在宿主机上访问容器服务
curl http://localhost:8080

# 进入正在运行的容器
podman exec -it web-server /bin/sh

# 查看Nginx配置文件
cat /etc/nginx/nginx.conf

# 退出但不停止容器(按Ctrl+P, Ctrl+Q)

深入理解容器原理

1. 容器的隔离机制

容器并非完整的虚拟机,它们与宿主机共享同一个内核,但通过Linux的命名空间和控制组实现隔离:

bash

# 查看容器的进程ID
podman inspect web-server --format '{{.State.Pid}}'

# 查看容器的命名空间信息
sudo ls -la /proc/<PID>/ns/

# 查看控制组信息
cat /proc/<PID>/cgroup

每个命名空间提供了不同的隔离环境:

  • PID命名空间:独立的进程ID空间

  • Network命名空间:独立的网络栈

  • Mount命名空间:独立的文件系统视图

  • UTS命名空间:独立的主机名和域名

2. 容器与虚拟机的对比

让我们通过一个实验直观感受两者的区别:

bash

# 1. 启动一个容器
podman run -d --name test-container docker.io/library/alpine:latest sleep 3600

# 2. 查看容器内的进程
podman exec test-container ps aux

# 3. 在宿主机查看同一个进程
CONTAINER_PID=$(podman inspect test-container --format '{{.State.Pid}}')
echo "容器内PID 1对应的宿主机PID是: $CONTAINER_PID"
ps aux | grep -E "PID|$CONTAINER_PID"

关键区别总结:

特性容器虚拟机
启动速度秒级分钟级
性能损耗低(<5%)高(15-30%)
隔离级别进程级别硬件级别
镜像大小MB级别GB级别
宿主机共享共享内核独立内核

构建自定义容器镜像

1. 创建Dockerfile

让我们创建一个简单的自定义应用并打包成容器镜像:

bash

# 创建项目目录
mkdir ~/myapp && cd ~/myapp

# 创建简单的Python应用
cat > app.py << 'EOF'
#!/usr/bin/env python3
from http.server import HTTPServer, BaseHTTPRequestHandler
import socket

class SimpleHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        hostname = socket.gethostname()
        message = f"""
        <html>
        <body>
            <h1>Hello from Container World!</h1>
            <p>Server Hostname: {hostname}</p>
            <p>Request Path: {self.path}</p>
            <p>This is my first custom container!</p>
        </body>
        </html>
        """
        self.wfile.write(message.encode())

if __name__ == '__main__':
    server = HTTPServer(('0.0.0.0', 8080), SimpleHandler)
    print('Server starting on port 8080...')
    server.serve_forever()
EOF

# 创建Dockerfile
cat > Dockerfile << 'EOF'
# 使用Alpine Python作为基础镜像
FROM docker.io/library/python:3.9-alpine

# 设置工作目录
WORKDIR /app

# 复制应用文件
COPY app.py /app/

# 暴露端口
EXPOSE 8080

# 运行应用
CMD ["python3", "app.py"]
EOF

2. 构建并运行自定义镜像

bash

# 构建镜像
podman build -t my-python-app .

# 查看构建的镜像
podman images | grep my-python-app

# 运行自定义应用
podman run -d --name myapp -p 8888:8080 my-python-app

# 测试应用
curl http://localhost:8888

# 或者用浏览器访问
firefox http://localhost:8888 &

容器网络深入探索

1. 理解容器网络模式

bash

# 查看容器网络配置
podman inspect web-server --format '{{.NetworkSettings}}' | python3 -m json.tool

# 创建自定义网络
podman network create my-network

# 在自定义网络中运行容器
podman run -d --name container1 --network my-network nginx:alpine
podman run -d --name container2 --network my-network nginx:alpine

# 测试容器间通信
podman exec container1 ping -c 3 container2

# 查看网络详情
podman network inspect my-network

2. 容器端口映射原理

端口映射是通过iptables实现的:

bash

# 查看NAT规则
sudo iptables -t nat -L -n

# 查看具体的端口转发规则
sudo iptables -t nat -L PREROUTING -n -v

容器存储管理

1. 使用卷(Volume)持久化数据

bash

# 创建卷
podman volume create my-data

# 运行使用卷的容器
podman run -d --name db-container \
  -v my-data:/var/lib/mysql \
  docker.io/library/mysql:8.0

# 查看卷信息
podman volume inspect my-data

# 在宿主机查看卷数据
sudo ls -la /var/lib/containers/storage/volumes/my-data/_data

2. 绑定挂载(Bind Mount)

bash

# 创建宿主机目录
mkdir ~/web-content

# 创建测试页面
echo "<h1>Hello from Bind Mount</h1>" > ~/web-content/index.html

# 运行容器并绑定挂载
podman run -d --name web-with-bind \
  -p 8081:80 \
  -v ~/web-content:/usr/share/nginx/html:ro \
  nginx:alpine

# 测试
curl http://localhost:8081

容器编排初探

1. 使用Podman Compose

bash

# 安装Podman Compose
sudo dnf install -y podman-compose

# 创建docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: '3'
services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html
  db:
    image: postgres:13-alpine
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - postgres-data:/var/lib/postgresql/data
volumes:
  postgres-data:
EOF

# 创建HTML目录
mkdir html
echo "<h1>Multi-Container App</h1>" > html/index.html

# 启动应用栈
podman-compose up -d

# 查看状态
podman-compose ps

# 停止应用栈
podman-compose down

最佳实践与安全建议

1. 安全准则

bash

# 1. 使用非root用户运行容器
podman run -d --name secure-app \
  --user 1000:1000 \
  nginx:alpine

# 2. 只读文件系统(除非必要)
podman run -d --name read-only-app \
  --read-only \
  nginx:alpine

# 3. 限制资源使用
podman run -d --name limited-app \
  --memory 256m \
  --cpu-quota 50000 \
  nginx:alpine

# 查看资源限制
podman stats

2. 日常维护命令

bash

# 查看容器资源使用
podman stats

# 清理无用资源
podman system prune -a

# 更新所有镜像
podman images --format "{{.Repository}}" | xargs -L1 podman pull

# 导出/导入容器
podman export web-server > web-container.tar
podman import web-container.tar my-nginx:exported

故障排除指南

常见问题及解决方案

bash

# 问题1: 端口被占用
# 错误信息: Error: port is already allocated
# 解决方案: 更改端口或停止占用端口的容器
podman run -d -p 8082:80 nginx:alpine

# 问题2: 镜像拉取失败
# 错误信息: Error: error pulling image
# 解决方案: 配置镜像加速器
mkdir -p ~/.config/containers
cat > ~/.config/containers/registries.conf << EOF
unqualified-search-registries = ["docker.io"]
[[registry]]
prefix = "docker.io"
location = "registry-1.docker.io"
[[registry.mirror]]
location = "mirror.ccs.tencentyun.com"
EOF

# 问题3: 权限不足
# 错误信息: permission denied
# 解决方案: 配置rootless模式或使用sudo
sudo podman run ...  # 临时解决方案
# 或正确配置subuid/subgid

学习路径建议

现在你已经点亮了第一个容器,接下来可以:

  1. 深入学习Dockerfile编写:掌握多阶段构建、构建优化

  2. 探索容器编排:学习Kubernetes基础知识

  3. 实践CI/CD流水线:将容器集成到自动化部署流程中

  4. 关注容器安全:学习镜像扫描、运行时安全

总结

通过今天的实践,我们不仅运行了第一个容器,还深入理解了:

  • 容器的核心价值:环境一致性、快速部署、资源高效利用

  • Podman的基本操作:拉取镜像、运行容器、管理生命周期

  • 容器底层原理:命名空间、控制组、联合文件系统

  • 实际应用场景:自定义镜像构建、数据持久化、多容器编排

容器技术是云原生时代的基石。掌握它,你就打开了现代应用部署的大门。记住,最好的学习方式就是不断实践——尝试部署你自己的应用到容器中,遇到问题并解决它。

祝你在容器化的道路上越走越远!如果有任何问题,欢迎在评论区交流讨论。

更多推荐