一、上期回顾

每天半小时,轻松学Docker第七篇:Docker对象查询与资源配额

二、案例 MariaDB服务部署

【实践01- MariaDB服务部署】

安装 MariaDB 服务。利用 mariadb:11.6.2-noble 镜像创建并启动 mariadb-studk 容器,通过 -v 参数将宿主机目录绑定到容器的数据库存储目录,并设置容器重启策略。

1) 拉取 mariadb:11.6.2-noble 镜像

rz@demo:~$ docker pull mariadb:11.6.2-noble
11.6.2-noble: Pulling from library/mariadb
de44b265507a: Pull complete
...
Digest: sha256:a9547599cd87d7242435aea6fda22a9d83e2c06d16c658ef70d2868b3d3f6a80
Status: Downloaded newer image for mariadb:11.6.2-noble
docker.io/library/mariadb:11.6.2-noble

rz@demo:~$ docker images
REPOSITORY    TAG             IMAGE ID       CREATED         SIZE
nginx         1.27-alpine     93f9c72967db   8 weeks ago     47MB
mariadb       11.6.2-noble    6722945a6940   2 months ago    407MB
mysql         8.0.40-debian   2d415917245b   3 months ago    610MB
hello-world   latest          d2c94e258dcb   21 months ago   13.3kB

2) 创建并启动 mariadb-studk 容器

rz@demo:~$ docker run -d --restart always --name mariadb-studk -v $HOME/data_mariadb:/var/lib/mysql -p 3310:3306 -e MARIADB_ROOT_PASSWORD=123456 mariadb:11.6.2-noble
67f61f0efddb313b89f80f592e2552ffe6bb73eb40534d00ae70a4cbc8f6f40f

rz@demo:~$ docker ps -a
CONTAINER ID   IMAGE                STATUS         PORTS      NAMES
67f61f0efddb   mariadb:11.6.2-noble Up 3 seconds   ...3310..  mariadb-studk

rz@demo:~$ ls $HOME/data_mariadb/
aria_log.00000001  ibdata1               multi-master.info   tc.log
aria_log_control   ib_logfile0           mysql               undo001
ddl_recovery.log   ibtmp1                performance_schema  undo002
ib_buffer_pool     mariadb_upgrade_info  sys                 undo003

说明mariadb:11.6.2-noble 镜像在创建容器时使用 MARIADB_ROOT_PASSWORD 环境变量设置数据库 root 用户的密码。

3) 验证容器自动重启策略

rz@demo:~$ sudo systemctl restart docker.service

rz@demo:~$ docker ps
CONTAINER ID   IMAGE                STATUS         PORTS       NAMES
67f61f0efddb   mariadb:11.6.2-noble Up 2 minutes   ...3310...  mariadb-studk

验证结果:Docker 引擎重启后,mariadb-studk 容器自动启动,验证了 --restart always 策略生效。

4) 创建数据库并验证数据持久化

# 连接 MariaDB 服务
rz@demo:~$ mysql -h 127.0.0.1 -P3310 -uroot -p123456
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 11.6.2-MariaDB-ubu2404 mariadb.org binary distribution
...

# 创建测试数据库
MariaDB [(none)]> create database mariadb_test;
Query OK, 1 row affected (0.001 sec)

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mariadb_test       |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.001 sec)

MariaDB [(none)]> exit;
Bye

# 在宿主机验证数据库目录
rz@demo:~$ ls $HOME/data_mariadb/
aria_log.00000001  ibdata1               mariadb_upgrade_info  sys      undo003
aria_log_control   ib_logfile0           multi-master.info     tc.log
ddl_recovery.log   ibtmp1                mysql                 undo001
ib_buffer_pool     mariadb_test          performance_schema    undo002

结果:在容器内创建的 mariadb_test 数据库,在宿主机的绑定目录中成功生成了对应的目录。

5) 清理资源

# 停止并删除容器
rz@demo:~$ docker stop mariadb-studk
mariadb-studk

rz@demo:~$ docker rm mariadb-studk
mariadb-studk

# 删除数据目录
rz@demo:~$ sudo rm -rf $HOME/data_mariadb/

rz@demo:~$ ls
debian-src  docker-ce  scripts

关键要点总结:

  • 使用 --restart always 确保容器在 Docker 守护进程启动时自动重启

  • 通过 -v 参数实现宿主机目录与容器目录的绑定挂载

  • 数据持久化:数据库文件存储在宿主机目录,容器删除后数据仍然保留

  • MariaDB 使用 MARIADB_ROOT_PASSWORD 环境变量设置 root 密码

三、案例 Ubuntu20.04环境部署

【实践02-Ubuntu20.04环境部署】

在实际开发与运维工作中,软件开发与系统运维往往由不同的团队负责,甚至软件开发本身也由多个小组构成。不同的群体很难保证技术层面的一致性,会使用不同的开发环境、操作系统(如 CentOS、Debian、Redhat、SUSE、Ubuntu 等发行版)和版本。

这些由不同环境与技术开发出来的软件和程序,对实施运维的工程师造成了极大的挑战。使用容器技术可以达到环境隔离的效果,保障在开发、测试、运维与部署阶段使用一致的环境,避免环境差异造成的软件缺陷。

部署 Ubuntu 20.04 容器环境:

# 创建并以交互方式运行 ubuntu-20.04LTS 容器
root@debase:~# docker run --rm -it --name ubuntu-20.04LTS ubuntu:20.04
Unable to find image 'ubuntu:20.04' locally
20.04: Pulling from library/ubuntu
13b7e930469f: Pull complete
Digest: sha256:8feb4d8ca5354def3d8fce243717141ce31e2c428701f6682bd2fafe15388214
Status: Downloaded newer image for ubuntu:20.04

验证容器系统版本:

# 在容器内查看系统版本信息
root@81ffc633d788:/# cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.6 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.6 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

# 退出容器
root@57eb0babf012:/# exit
exit

验证容器状态和镜像:

# 查看容器状态(已自动删除)
rz@demo:~$ docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

# 查看本地镜像
rz@demo:~$ docker images
REPOSITORY    TAG             IMAGE ID       CREATED         SIZE
nginx         1.27-alpine     93f9c72967db   8 weeks ago     47MB
mariadb       11.6.2-noble    6722945a6940   2 months ago    407MB
mysql         8.0.40-debian   2d415917245b   3 months ago    610MB
hello-world   latest          d2c94e258dcb   21 months ago   13.3kB
ubuntu        20.04           b6f507652425   3 years ago     135MB

实践要点说明:

  1. 镜像拉取:由于本地没有 ubuntu:20.04 镜像,使用 docker run 命令时会自动拉取镜像至本地

  2. 交互模式:使用 -it 参数以交互方式进入容器,便于操作和调试

  3. 自动清理:使用 --rm 参数,容器退出后自动删除,避免资源残留

  4. 环境验证:通过 /etc/os-release 文件确认容器内系统为 Ubuntu 20.04.7 LTS

  5. 环境一致性:通过容器化技术,可以确保在任何支持 Docker 的环境中都能获得完全一致的 Ubuntu 20.04 运行环境

四、案例 Nginx服务部署

【实践03-Nginx服务部署】

在实际的Web服务部署中,Nginx作为高性能的HTTP和反向代理服务器被广泛使用。通过容器化部署Nginx,可以快速搭建Web服务,并保持环境的一致性。

1) 拉取Nginx镜像

# 拉取官方Nginx镜像(默认最新版本)
rz@demo:~$ docker pull nginx:latest
latest: Pulling from library/nginx
...
Digest: sha256:aa8a62f5c81c2f4c5b55c63946caf4d6a2b1e2f5c5f5c5d5c5d5c5d5c5d5c5d
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest

# 查看已拉取的镜像
rz@demo:~$ docker images
REPOSITORY    TAG             IMAGE ID       CREATED         SIZE
nginx         latest          1f2bf2d6c5d3   2 weeks ago     187MB
mariadb       11.6.2-noble    6722945a6940   2 months ago    407MB
mysql         8.0.40-debian   2d415917245b   3 months ago    610MB
ubuntu        16.04           b6f507652425   3 years ago     135MB

2) 创建并运行Nginx容器

# 创建并启动nginx容器,映射端口80到宿主机的8080端口
rz@demo:~$ docker run -d --name nginx-web -p 8080:80 nginx:latest
c5d3e6f8a9b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8

# 查看容器运行状态
rz@demo:~$ docker ps
CONTAINER ID   IMAGE          COMMAND                  STATUS         PORTS                  NAMES
c5d3e6f8a9b2   nginx:latest   "nginx -g 'daemon off;'" Up 5 seconds   0.0.0.0:8080->80/tcp   nginx-web

3) 验证Nginx服务

# 使用curl测试Nginx服务
rz@demo:~$ curl http://localhost:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
...
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
...
</body>
</html>

# 或者在浏览器中访问 http://localhost:8080

4) 自定义Nginx配置(挂载自定义配置)

# 创建存放Nginx配置的目录
rz@demo:~$ mkdir -p ~/nginx-config
rz@demo:~$ mkdir -p ~/nginx-html

# 复制容器内的默认配置到宿主机(可选)
rz@demo:~$ docker cp nginx-web:/etc/nginx/nginx.conf ~/nginx-config/
rz@demo:~$ docker cp nginx-web:/usr/share/nginx/html/ ~/nginx-html/

# 停止并删除原有容器
rz@demo:~$ docker stop nginx-web
rz@demo:~$ docker rm nginx-web

# 使用自定义配置重新创建容器
rz@demo:~$ docker run -d --name nginx-web \
  -p 8080:80 \
  -v $HOME/nginx-html:/usr/share/nginx/html \
  -v $HOME/nginx-config/nginx.conf:/etc/nginx/nginx.conf \
  nginx:latest

5) 创建自定义首页

# 创建自定义HTML页面
rz@demo:~$ cat > ~/nginx-html/index.html << EOF
<!DOCTYPE html>
<html>
<head>
    <title>自定义Nginx页面</title>
</head>
<body>
    <h1>欢迎使用Docker部署的Nginx服务!</h1>
    <p>当前时间: <span id="datetime"></span></p>
    <script>
        document.getElementById('datetime').textContent = new Date().toLocaleString();
    </script>
</body>
</html>
EOF

# 重启容器使配置生效
rz@demo:~$ docker restart nginx-web

6) 验证自定义配置

# 再次访问验证自定义页面
rz@demo:~$ curl http://localhost:8080
<!DOCTYPE html>
<html>
<head>
    <title>自定义Nginx页面</title>
</head>
<body>
    <h1>欢迎使用Docker部署的Nginx服务!</h1>
    <p>当前时间: <span id="datetime"></span></p>
    <script>
        document.getElementById('datetime').textContent = new Date().toLocaleString();
    </script>
</body>
</html>

7) 设置容器自动重启

# 重新创建容器并设置自动重启策略
rz@demo:~$ docker stop nginx-web
rz@demo:~$ docker rm nginx-web

rz@demo:~$ docker run -d --name nginx-web \
  --restart always \
  -p 8080:80 \
  -v $HOME/nginx-html:/usr/share/nginx/html \
  -v $HOME/nginx-config/nginx.conf:/etc/nginx/nginx.conf \
  nginx:latest

8) 验证自动重启策略

# 重启Docker服务,验证容器是否自动启动
rz@demo:~$ sudo systemctl restart docker

# 等待几秒后检查容器状态
rz@demo:~$ docker ps
CONTAINER ID   IMAGE          COMMAND                  STATUS         PORTS                  NAMES
c5d3e6f8a9b2   nginx:latest   "nginx -g 'daemon off;'" Up 10 seconds  0.0.0.0:8080->80/tcp   nginx-web

9) 清理资源(可选)

# 停止并删除容器
rz@demo:~$ docker stop nginx-web
rz@demo:~$ docker rm nginx-web

# 删除自定义配置文件
rz@demo:~$ rm -rf ~/nginx-config ~/nginx-html

部署要点总结:

  1. 快速部署:使用官方Nginx镜像可快速搭建Web服务

  2. 端口映射:通过-p参数将容器端口映射到宿主机

  3. 数据持久化:使用-v参数挂载配置文件和网站目录

  4. 自动重启:设置--restart always确保服务高可用

  5. 灵活配置:支持自定义配置和静态资源

  6. 环境一致:容器化部署确保开发、测试、生产环境一致性

五、案例 页面访问计数Python Web服务部署

【实践04-Python Web服务开发与部署】

开发Python Web服务,功能要求为:用户访问时显示服务器主机名、服务器IP地址、服务器当前时间、当前Web服务累计被访问次数,以及客户端的IP地址。通过容器化部署实现服务的高可用和数据持久化。

整体方案思路:

  1. 选择Python环境基础镜像

  2. 编写Python Web代码

  3. 创建自定义镜像

  4. 部署多个容器实例

  5. 验证自动重启策略

  6. 测试数据持久化


一、选择Python环境基础镜像

选择python:3.13-slim作为基础镜像,这是基于Debian系统构建的轻量级镜像。

root@debase:~# docker pull python:3.13-slim
3.13-slim: Pulling from library/python
8c7716127147: Pull complete
31fd2a94d723: Pull complete
66b685f2f76b: Pull complete
7d456e82f89b: Pull complete
Digest: sha256:5f55cdf0c5d9dc1a415637a5ccc4a9e18663ad203673173b8cda8f8dcacef689
Status: Downloaded newer image for python:3.13-slim
docker.io/library/python:3.13-slim
root@debase:~# docker images
REPOSITORY    TAG               IMAGE ID       CREATED        SIZE
python        3.13-slim         7b444340715d   7 weeks ago    117MB

二、编写Python Web代码

1) 创建项目目录

rz@demo:~$ mkdir $HOME/python-web
rz@demo:~$ ls
debian-src  docker-ce  python-web  scripts

2) 编写Flask应用代码 ($HOME/python-web/app.py)

from flask import Flask, request, render_template_string
from datetime import datetime
import sqlite3, socket

app = Flask(__name__)

# SQLite数据库文件
DATABASE = '/root/sqlite/app.db'

# 初始化数据库和表
def init_db():
    conn = sqlite3.connect(DATABASE)
    cursor = conn.cursor()
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS visits (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            count INTEGER NOT NULL DEFAULT 0
        )
    ''')
    # 如果表是空的,插入一个初始记录
    cursor.execute('SELECT * FROM visits')
    if cursor.fetchone() is None:
        cursor.execute('INSERT INTO visits (count) VALUES (0)')
    conn.commit()
    conn.close()

# 获取当前的访问次数,并增加1
def get_and_increment_visit_count():
    conn = sqlite3.connect(DATABASE)
    cursor = conn.cursor()
    cursor.execute('SELECT count FROM visits')
    count = cursor.fetchone()[0]
    cursor.execute('UPDATE visits SET count = ?', (count + 1,))
    conn.commit()
    conn.close()
    return count + 1

# HTML模板
template = '''
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>Page Visit Counter</title>
  </head>
  <body>
    <h1>The Server Name is: {{ server_hostname }}</h1>
    <h1>The Server IP is: {{ server_ip }}</h1>
    <h1>The current server time is: {{ server_time }}</h1>
    <h1>This page has been visited {{ visit_count }} times.</h1>
    <h1>Your IP Address is: {{ client_ip }}</h1>
  </body>
</html>
'''

@app.route('/')
def index():
    # 获取Web累计访问次数
    visit_count = get_and_increment_visit_count()
    # 获取访问者IP地址
    client_ip = request.remote_addr
    # 获取主机名
    server_hostname = socket.gethostname()
    # 获取服务器IP地址
    server_ip = socket.gethostbyname(server_hostname)
    # 获取当前服务器时间
    server_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    return render_template_string(template, 
                                server_hostname=server_hostname,
                                server_ip=server_ip,
                                server_time=server_time,
                                visit_count=visit_count,
                                client_ip=client_ip)

if __name__ == '__main__':
    init_db()  # 初始化数据库
    app.run(debug=True, host='0.0.0.0', port=80)

三、创建自定义镜像

1) 创建临时测试容器

rz@demo:~$ docker run -it --name python-tmp -p 8080:80 python:3.13-slim /bin/bash

2) 在容器内创建目录结构

root@d4ade3e6816f:/# mkdir /root/sqlite
root@d4ade3e6816f:/# mkdir /root/python-web

3) 从宿主机拷贝文件到容器

rz@demo:~$ docker cp $HOME/python-web/app.py python-tmp:/root/python-web/
rz@demo:~$ docker cp /etc/apt/sources.list.d/ustc-debian.list python-tmp:/etc/apt/sources.list.d/debian.list

4) 在容器内安装依赖

root@d4ade3e6816f:/# apt update
root@d4ade3e6816f:/# apt install curl -y
root@d4ade3e6816f:/# pip install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple Flask

5) 测试应用

root@d4ade3e6816f:/# python /root/python-web/app.py

6) 创建自定义镜像

rz@demo:~$ docker commit -a "demo" -m "Web Server" python-tmp python:count-ip-v1
rz@demo:~$ docker rm -f python-tmp
python-tmp

四、部署多容器实例

1) 创建数据目录

rz@demo:~$ mkdir $HOME/python-web/db1
rz@demo:~$ mkdir $HOME/python-web/db2
rz@demo:~$ tree $HOME/python-web/
/home/rz/python-web/
├── app.py
├── db1
└── db2

2) 创建并启动容器

# 启动第一个容器实例
rz@demo:~$ docker run -d --name python-web1 --restart always \
  -p 8080:80 \
  -v $HOME/python-web/db1/:/root/sqlite/ \
  python:count-ip-v1 python /root/python-web/app.py

# 启动第二个容器实例  
rz@demo:~$ docker run -d --name python-web2 --restart always \
  -p 8082:80 \
  -v $HOME/python-web/db2/:/root/sqlite/ \
  python:count-ip-v1 python /root/python-web/app.py

rz@demo:~$ docker ps
CONTAINER ID   IMAGE               STATUS        PORTS            NAMES
d37a519d807b   python:count-ip-v1   "python /root/python…"   3 seconds ago        Up 2 seconds        0.0.0.0:8082->80/tcp, [::]:8082->80/tcp   python-web2
2f205ba9da1d   python:count-ip-v1   "python /root/python…"   About a minute ago   Up About a minute   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp   python-web1

3) 验证数据持久化

root@debase:/home/python-web# tree
.
├── app.py
├── db1
│   └── app.db
└── db2
    └── app.db

3 directories, 3 files

五、验证服务功能

1) 测试服务访问

# 访问第一个容器
rz@demo:~$ curl http://localhost:8080

# 访问第二个容器
rz@demo:~$ curl http://localhost:8082

预期输出包含:

  • 服务器主机名(容器ID)

  • 服务器IP地址

  • 当前服务器时间

  • 访问次数统计

  • 客户端IP地址

2) 验证自动重启策略

rz@demo:~$ sudo systemctl restart docker.service
rz@demo:~$ docker ps
CONTAINER ID   IMAGE               STATUS        PORTS            NAMES
0595845549fe   python:count-ip-v1  Up 54 seconds ...8082->80...   python-web2
d41d772175b6   python:count-ip-v1  Up 54 seconds ...8080->80...   python-web1

六、验证数据持久化

1) 删除并重建容器

rz@demo:~$ docker stop python-web1 python-web2
rz@demo:~$ docker rm python-web1 python-web2

# 重新创建容器(使用相同的数据目录)
rz@demo:~$ docker run -d --name python-web1 --restart always \
  -p 8080:80 \
  -v $HOME/python-web/db1/:/root/sqlite/ \
  python:count-ip-v1 python /root/python-web/app.py

2) 验证访问次数持续累计

rz@demo:~$ curl http://localhost:8080
# 访问次数应该从上次的数值继续累计

七、清理资源

# 停止并删除所有容器
rz@demo:~$ docker rm -f $(docker ps -qa)

# 删除数据目录
rz@demo:~$ rm -rf $HOME/python-web/db1/
rz@demo:~$ rm -rf $HOME/python-web/db2/

关键技术要点

  1. 环境一致性:通过容器化确保开发、测试、生产环境一致

  2. 数据持久化:使用绑定挂载实现数据库文件持久化存储

  3. 服务高可用:配置--restart always确保服务自动恢复

  4. 多实例部署:通过不同端口和数据目录实现多实例隔离

  5. 资源隔离:每个容器实例拥有独立的数据和运行环境

六、总结与预告

1、下节课程预告

每天半小时,轻松学Docker第九篇:Dockerfile的镜像构建

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐