1. 什么是docker
Docker是一种开源的容器化平台,它通过使用容器来简化应用程序的交付和部署。容器是一种轻量级的虚拟化技术,它允许将应用程序及其依赖项打包在一起,以便在不同的环境中运行。Docker提供了一个可移植、可扩展和可靠的环境,使开发人员能够更轻松地构建、发布和管理应用程序。
简单的一句话说,Docker让部署更容易,实现了程序、环境和用户数据之间的完全分离。而对于软件开发或者运维而言,充分的解耦无疑是带来了巨大的维护优势。
这里为了快速上手做铺垫,如果对docker只闻其名,未曾用过的话。可以把以下几个经常出现的概念这么理解:
-
镜像(
Image
): 这里的镜像,可以等于任何描述的包含所有内容的文件,包括命令行参数、环境变量等等。简单来说,如果用程序exe文件和正在运行的进程之间的关系做比较的话,镜像就是相当于程序的exe文件。只是区别开来的是,镜像不止是一个程序,而是一个正在运行的虚拟系统,只是很精简,里面有特定的必须得程序。 -
容器(
Container
):容器按照上面解释,其实就是某一个镜像正在运行起来的那个虚拟的环境。
如果熟悉程序和进程的差异,那么马上可以联想到这样一件事,对的,一个镜像可以生成多个容器,就好像一个程序可以启动多个,从而获取多个一样程序的进程一样。例如打开2个QQ同时登陆2个QQ号。知道了以上两个重要的概念,理解后面的东西就容易多了。
2. docker能做什么
Docker提供了一种简化和加速应用程序开发和交付过程的方法。它具有以下特点和优势:
a. 隔离性与可移植性: Docker容器提供了强大的隔离性,使应用程序能够在不同的环境中以一致的方式运行,而无需担心环境差异带来的问题。这种可移植性使得开发人员可以在本地开发、测试,然后将应用程序轻松地部署到生产环境中。
b. 资源效率: 与传统的虚拟机相比,Docker容器非常轻量级,启动和停止速度快,并且占用更少的系统资源。容器共享主机操作系统的内核,因此多个容器可以在同一台主机上高效运行。
c. 快速部署和扩展: Docker容器可以在几秒钟内启动,使得应用程序的部署变得非常快速和可靠。此外,可以根据应用程序的需求快速扩展容器,以满足高负载和流量的要求。
d. 简化依赖项管理: Docker允许开发人员将应用程序和其依赖项打包在一个容器中,以便简化依赖项管理。这意味着开发人员可以确保在不同的环境中使用相同的依赖项版本,从而避免了因环境差异引起的问题。
3. docker跨平台咋样
Docker在跨平台方面表现出色。它可以运行在不同的操作系统上,包括Linux、Windows和macOS等。Docker利用了操作系统级虚拟化技术,如Linux的命名空间和控制组,使得容器可以在不同的操作系统上运行,而无需进行修改。这使得开发人员可以在不同的开发环境中共享和交付容器,并确保应用程序在各个平台上一致运行。
这里需要额外说明一下,Docker的跨平台特性是需要额外操作才能实现的,默认docker直接的跨平台还需要保证宿主机架构和镜像架构一致,即x86_x64的系统只能运行x86_x64的镜像,ARM64的系统,可以运行armv5,armeabi-v7a和arm64-v8a的镜像。这种跨平台,在熟悉交叉编译的人眼中,属于跨了平台,但没有完全跨。
对于嵌入式工程师而言,利用docker跨平台的特性,我们可以在没有板子的条件下,依旧方便的运行板子中的程序。
对于运维而言而言,docker镜像可以让你免去不同组件依赖的环境不同而需要繁琐部署,甚至产生组件和组件之间冲突而无法部署的问题。
3.1 如何实现真的跨平台
这里以如何在x86平台下运行ARM的Docker镜像作为介绍。
虽然Docker最初是为x86架构设计的,但借助跨平台的技术,也可以在x86平台上运行ARM架构的Docker镜像。这种情况下,需要使用一种称为"多架构镜像"的特性来实现。
以下是在x86平台上运行ARM镜像的基本步骤:
3.1.1 确认平台支持
首先,确保你的x86平台支持在其中运行ARM架构的容器。这通常需要在BIOS或UEFI设置中启用相应的虚拟化扩展(例如,Intel VT-x或AMD-V)。
3.1.2 安装QEMU和binfmt_misc
在x86平台上运行ARM镜像需要使用QEMU模拟器来实现。确保已在主机上安装QEMU模拟器和binfmt_misc内核模块。
3.1.3 配置binfmt_misc
通过配置binfmt_misc内核模块,将ARM架构的镜像关联到QEMU模拟器。这可以通过执行以下命令完成:
docker run --privileged linuxkit/binfmt:v0.8
这将加载binfmt_misc内核模块,并配置Docker以将ARM架构关联到QEMU模拟器。
3.1.4 拉取并运行ARM镜像
现在,可以拉取并运行ARM架构的Docker镜像了。可以使用常规的docker pull和docker run命令,指定所需的ARM镜像。Docker会自动使用QEMU模拟器来运行ARM镜像。
例如,要拉取并运行一个基于ARM的镜像,可以执行以下命令:
docker pull arm32v7/alpine
docker run --rm -it arm32v7/alpine /bin/sh
这将拉取Alpine Linux的ARM版本,并在x86平台上运行一个交互式容器。
需要注意的是,由于在x86平台上模拟ARM架构,性能可能会有所下降。因此,这种方法更适合于测试和开发目的,而不是在生产环境中广泛使用。通过使用QEMU模拟器和多架构镜像的特性,我们可以在x86平台上运行ARM架构的Docker镜像。这为开发人员提供了更大的灵活性,使他们能够在不同的硬件平台上测试和部署应用程序。
可以直接跨平台运行镜像的意义对嵌入式工程师说不言而喻,往往调试目标程序都需要一块目标板,系统测试或者集成测试的时候,测试工程师更是要借用很多目标板去实现测试上层平台的目的,但我们可以直接把嵌入式程序的版本制作成docker镜像交付给测试工程师,并把对应的log目录和sql目录映射到不同的地方,这样他们即便启动50,100台,也毫无压力。
4. 制作一个docker镜像
制作一个Docker镜像需要以下步骤:
4.1 编写Dockerfile
创建一个Dockerfile,并在其中定义构建镜像的步骤。这可能包括选择基础镜像、复制应用程序代码、安装依赖项、设置环境变量等。
4.1.1 如果觉得Dockfile上手比较复杂,想快速上手,请看这里
如果您不想使用Dockerfile来制作自定义镜像,还有其他方法可以创建自制镜像。以下是一种常见的方法:
4.1.1.1 创建一个容器
首先,您可以通过运行一个基础镜像的容器来开始创建自制镜像。选择与您要构建的应用程序或服务相关的基础镜像,例如Ubuntu、Alpine或CentOS。
运行容器的命令如下:
docker run -it --name mycontainer <基础镜像>
这将创建一个名为mycontainer的容器,并进入交互式模式。
4.1.1.2 在容器中进行配置和修改
进入容器后,您可以在其中进行配置和修改。安装所需的软件包、添加文件、更改配置等。您可以使用适用于容器内部的任何命令和工具来进行修改。
# 在容器内执行所需的配置命令
apt-get update
apt-get install -y <软件包>
4.1.1.3 提交容器为镜像
当您完成容器的配置和修改后,可以将其提交为一个新的镜像。
在宿主机上打开另一个终端窗口,运行以下命令:
docker commit mycontainer <自制镜像名称>
这将使用容器的当前状态创建一个新的镜像,并指定一个自定义的镜像名称。
4.1.1.4 测试和使用自制镜像
创建镜像后,您可以使用docker run命令来测试和运行该镜像:
docker run -it <自制镜像名称>
您可以验证自制镜像是否按预期工作,并根据需要进行进一步的修改和调整。
需要注意的是,这种方法可能不如使用Dockerfile来管理镜像构建过程灵活和可维护。Dockerfile提供了更结构化、可重复和可自动化的方法来构建镜像。但如果您只需创建简单的自制镜像,上述方法是一种替代方案。
总结而言,通过创建容器、进行配置和修改,并将其提交为新的镜像,您可以创建自制镜像而无需使用Dockerfile。这种方法对于一些简单的自定义需求可能是合适的,但对于更复杂的镜像构建和管理,建议使用Dockerfile来实现更好的可重复性和可维护性。
4.2 构建镜像
在Dockerfile所在的目录下,使用docker build命令来构建镜像。该命令将根据Dockerfile的定义,自动构建镜像并生成一个唯一的镜像标签。
4.3 推送镜像(可选)
如果需要将镜像分享给其他人或在不同的环境中使用,可以将镜像推送到Docker镜像仓库。可以使用docker push命令将镜像上传到Docker Hub或其他私有仓库。
当然如果不想推送到公网,你可以使用Docker save和Docker import来生成tar包镜像,进行离线交付传输。
制作Docker镜像可以让我们更轻松地共享和交付应用程序,并确保应用程序在不同环境中一致运行。
总结而言,Docker是一个强大的容器化平台,可以简化应用程序的交付和部署过程。它具有跨平台能力、高效的资源利用、快速部署和扩展的优势,并且能够简化依赖项管理。通过使用Docker,开发人员可以更加高效地开发、测试和部署应用程序,从而提高开发团队的生产力和应用程序的可靠性。
5. 这里结合前一篇博客的WEB服务后台部署进行介绍如何Docker化
这里之前使用Nginx
+ supervisor
+ gunicorn
+ Flask
+ Redis
+ Postgresql
搭建过一个简单的Web服务器。Web数据流的一生——从前端界面到后端数据,以及万级QPS的后台服务搭建。对于这个服务器的部署迁移,使用docker技术就帮上了大忙。
5.1 不用docker之前需要做什么
不使用docker的时候,一般需要先去vps上找一个合适的系统,然后在系统里装上依赖环境,依次需要安装Nginx
+ supervisor
+ gunicorn
+ Flask
+ Redis
+ Postgresql
,比较麻烦的是Postgresql还要进行一定的配置,设置账号,设置权限等等。对于Flask和gunicorn,一般还需要找到合适的版本和对应的环境正确,大多数时候,总是免不了安装miniconda
等Python环境快速转换轮子。
以上步骤弄好之后,还一定要试运行,确保万无一失,往往经验丰富的运维会弄一个专门的部署脚本,但是由于vps的依赖源更新,总是不可避免的会有部分翻车现象。
5.2 使用docker后可以如何简化
使用docker之后,在vps中安装了docker,然后把开发环境的自制镜像拉去过来即可(如果你推送到了dockerHub,没推送的话,把镜像tar包复制到vps一样可以import进来)。
接下来不用担心环境问题,依次把需要的几个组件起来就好,这里给一个样例启动脚本。(由于项目不复杂就没整Docker Compose了,而实际上Docker Compose只是把下面的脚本用了一个Dokcer自制的语言体现出来,逻辑没区别。)
先看启动脚本:
docker run -p 8080:8080 -p 80:80 -p 8081:81 -p 8088:5000 \
--name flaskapp --restart always \
-v /Users/adamxiao/Documents/DDJN/src/nginx.conf:/etc/nginx/nginx.conf -v /Users/adamxiao/Documents/DDJN/web/:/home/DDJN/web -v /Users/adamxiao/Documents/DDJN/app/:/home/DDJN/app \
-d nginx
docker run -v /Users/adamxiao/Documents/DDJN/:/home/ -v /Users/adamxiao/Documents/DDJN/src/conf/supervisor_app.conf:/etc/supervisor/conf.d/supervisor_app.conf -v /Users/adamxiao/Documents/DDJN/src/conf/supervisor_ver.conf:/etc/supervisor/conf.d/supervisor_ver.conf \
--name flaskcore
--network=container:flaskapp \
-d aiyanzielf/flask:0.2 sh /home/src/flask.sh
docker run --name flaskredis --restart always --network=container:flaskapp \
-d redis
docker run -d \
--name flasksql \
-e POSTGRES_USER=aiyanzielf \
-e POSTGRES_PASSWORD=************ \
-e POSTGRES_DB=ddjn \
-v /Users/adamxiao/Documents/DDJN/postgres:/var/lib/postgresql/data \
-v /Users/adamxiao/Documents/DDJN/:/home/ \
--network=container:flaskapp \
--restart always \
postgres
对docker run命令不太熟的朋友可以先跳过去看一下,docker run。
这里分别启动了4个镜像,nginx
, flask
, redis
, postgresql
。上部分中介绍的superviosr
和gunicorn
我把官方的flask改了改直接丢了进去。这样整个服务其实就已经部署好了。
这里回到最前面介绍的,docker的作用是把程序和数据分离,通过配置也能够发现,nginx等服务的配置文件实际上并不在镜像中,而是通过挂载,把外面保存好的配置映射到容器内,从而实现了无论是什么配置,都无需修改镜像的目的。
这里还需要注意的一点是,如果在同一个环境中运行多个镜像,容器之间实际上是隔离的,那么这几个组件之间是怎么通信的呢?docker中新增了一个docker网络的概念,可以把所有的容器都添加到一个网络中,这样虽然他们之间是相互独立的,但在一个网络内通信就好像是在一个环境中一样, --network=container:flaskapp
启动脚本中,除了nginx
其他的服务均由这个指令,而nginx
的容器名被设置为flaskapp
,通过这样的手法,4个服务均在一个网络内,整个应用服务就能顺利运行起来了。
#这里给出启动脚本对应 docker-compose.yml
# 在同等目录下,直接运行docker-compose up可以直接前台批量启动
# docker-compose up -d可以直接后台批量启动
version: '3.8'
services:
nginx:
build: .
image: nginx:latest
container_name: flaskapp
restart: always
ports:
- 8080:8080
- 80:80
- 8081:81
- 8088:55555
volumes:
- /Users/adamxiao/Documents/DDJN/src/nginx.conf:/etc/nginx/nginx.conf
- /Users/adamxiao/Documents/DDJN/web/:/home/DDJN/web
- /Users/adamxiao/Documents/DDJN/app/:/home/DDJN/app
networks:
- mynetwork
flask:
build: .
image: ddbackbone:latest
container_name: flaskcore
restart: always
volumes:
- /Users/adamxiao/Documents/DDJN/apk/:/home/DDJN/apk
environment:
- DEBUG=True
network_mode: "service:nginx"
redis:
build: .
image: redis:latest
container_name: flaskredis
restart: always
network_mode: "service:nginx"
db:
build: .
image: postgres:latest
container_name: flasksql
restart: always
volumes:
- /Users/adamxiao/Documents/DDJN/postgres:/var/lib/postgresql/data
- /Users/adamxiao/Documents/DDJN/:/home/
environment:
- POSTGRES_USER=aiyanzielf
- POSTGRES_PASSWORD=******
- POSTGRES_DB=ddjn
network_mode: "service:nginx"
networks:
mynetwork:
driver: bridge
driver_opts:
com.docker.network.container_mode: 1
docker-compose的好处是可以在前台方便的批量看到每个容器的实时输出,好像你在一个系统中一起启动这些服务一样,这里贴出我的demo服务器启动的打印:
[+] Building 0.0s (0/0)
[+] Running 4/0
✔ Container flaskapp Created 0.0s
✔ Container flasksql Recreated 0.0s
✔ Container flaskcore Recreated 0.0s
✔ Container flaskredis Recreated 0.0s
Attaching to flaskapp, flaskcore, flaskredis, flasksql
flaskapp | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
flaskapp | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
flaskapp | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
flaskapp | 10-listen-on-ipv6-by-default.sh: info: IPv6 listen already enabled
flaskapp | /docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
flaskapp | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
flaskapp | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
flaskapp | /docker-entrypoint.sh: Configuration complete; ready for start up
flaskapp | 2023/07/03 16:38:30 [notice] 1#1: using the "epoll" event method
flaskapp | 2023/07/03 16:38:30 [notice] 1#1: nginx/1.25.1
flaskapp | 2023/07/03 16:38:30 [notice] 1#1: built by gcc 12.2.0 (Debian 12.2.0-14)
flaskapp | 2023/07/03 16:38:30 [notice] 1#1: OS: Linux 5.15.49-linuxkit-pr
flaskapp | 2023/07/03 16:38:30 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
flaskapp | 2023/07/03 16:38:30 [notice] 1#1: start worker processes
flaskapp | 2023/07/03 16:38:30 [notice] 1#1: start worker process 22
flaskapp | 2023/07/03 16:38:30 [notice] 1#1: start worker process 23
flaskapp | 2023/07/03 16:38:30 [notice] 1#1: start worker process 24
flaskapp | 2023/07/03 16:38:30 [notice] 1#1: start worker process 25
flaskredis | 1:C 03 Jul 2023 16:38:30.209 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
flaskredis | 1:C 03 Jul 2023 16:38:30.209 # Redis version=7.0.11, bits=64, commit=00000000, modified=0, pid=1, just started
flaskredis | 1:C 03 Jul 2023 16:38:30.209 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
flaskredis | 1:M 03 Jul 2023 16:38:30.209 * monotonic clock: POSIX clock_gettime
flaskredis | 1:M 03 Jul 2023 16:38:30.210 * Running mode=standalone, port=6379.
flaskredis | 1:M 03 Jul 2023 16:38:30.210 # Server initialized
flaskredis | 1:M 03 Jul 2023 16:38:30.214 * Loading RDB produced by version 7.0.11
flaskredis | 1:M 03 Jul 2023 16:38:30.214 * RDB age 52 seconds
flaskredis | 1:M 03 Jul 2023 16:38:30.214 * RDB memory usage when created 0.85 Mb
flaskredis | 1:M 03 Jul 2023 16:38:30.214 * Done loading RDB, keys loaded: 0, keys expired: 0.
flaskredis | 1:M 03 Jul 2023 16:38:30.214 * DB loaded from disk: 0.000 seconds
flaskredis | 1:M 03 Jul 2023 16:38:30.214 * Ready to accept connections
flaskcore | /usr/lib/python3/dist-packages/supervisor/options.py:474: UserWarning: Supervisord is running as root and it is searching for its configuration file in default locations (including its current working directory); you probably want to specify a "-c" argument specifying an absolute path to a configuration file for improved security.
flaskcore | self.warnings.warn(
flaskcore | No config updates to processes
flasksql |
flasksql | PostgreSQL Database directory appears to contain a database; Skipping initialization
flasksql |
flasksql | 2023-07-03 16:38:30.979 UTC [1] LOG: starting PostgreSQL 15.3 (Debian 15.3-1.pgdg120+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
flasksql | 2023-07-03 16:38:30.979 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
flasksql | 2023-07-03 16:38:30.979 UTC [1] LOG: listening on IPv6 address "::", port 5432
flasksql | 2023-07-03 16:38:30.982 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
flasksql | 2023-07-03 16:38:30.992 UTC [30] LOG: database system was shut down at 2023-07-03 16:37:38 UTC
flasksql | 2023-07-03 16:38:31.011 UTC [1] LOG: database system is ready to accept connections
flaskcore | app: stopped
flaskcore | app: started
flaskcore | verd: stopped
flaskcore | verd: started
^CGracefully stopping... (press Ctrl+C again to force)
Aborting on container exit...
[+] Stopping 4/4
✔ Container flasksql Stopped 0.1s
✔ Container flaskcore Stopped 10.2s
✔ Container flaskredis Stopped 0.1s
✔ Container flaskapp Stopped 0.1s
canceled
Appendix Docker 常用指令
1. 镜像操作指令
docker pull <镜像名称>:从镜像仓库中拉取指定的镜像。
docker build -t <镜像名称> <Dockerfile路径>:根据Dockerfile构建一个镜像。
docker push <镜像名称>:将本地的镜像推送到镜像仓库中。
docker images:列出本地的镜像列表。
docker rmi <镜像ID>:删除指定的镜像。
2. 容器操作指令
docker run <镜像名称>:创建并运行一个容器。
docker start <容器ID>:启动一个已停止的容器。
docker stop <容器ID>:停止一个正在运行的容器。
docker restart <容器ID>:重启一个容器。
docker rm <容器ID>:删除一个容器。
docker ps:列出正在运行的容器列表。
docker ps -a:列出所有容器,包括已停止的容器。
2.1 这里针对最常用的docker run进行扩展
docker run是一个常用的Docker命令,用于创建并运行容器。除了基本的用法之外,还有一些常见的扩展指令可以用于更精细地控制容器的行为和配置。下面是一些常见的docker run扩展指令的使用和解释:
2.1.1 端口映射
docker run -p <主机端口>:<容器端口> <镜像名称>
此命令用于将容器内部的端口映射到主机上的指定端口。例如,docker run -p 8080:80 nginx将运行一个Nginx容器,并将容器内部的80端口映射到主机的8080端口。
2.1.2 环境变量设置
docker run -e <变量名>=<变量值> <镜像名称>
通过此命令可以设置容器的环境变量。可以多次使用-e选项来设置多个环境变量。例如,docker run -e MYSQL_ROOT_PASSWORD=password mysql将在MySQL容器中设置一个名为MYSQL_ROOT_PASSWORD的环境变量,并将其值设置为password。
2.1.3 挂载数据卷
docker run -v <主机路径>:<容器路径> <镜像名称>
使用此命令可以将主机上的目录或文件挂载到容器内部的指定路径。例如,docker run -v /host/data:/container/data nginx将主机上的/host/data目录挂载到容器内的/container/data路径。
2.1.4 后台运行
docker run -d <镜像名称>
通过添加-d选项,容器将在后台以守护进程方式运行。这样可以将容器放入后台运行,而不会阻塞当前终端。
2.1.5 容器命名
docker run --name <容器名称> <镜像名称>
使用此命令可以为容器指定一个自定义的名称,而不是由Docker自动生成一个随机名称。
2.1.6 交互式模式
docker run -it <镜像名称> <命令>
通过添加-it选项,容器将以交互式模式运行,并且终端会连接到容器的标准输入和输出。这常用于进入容器的Shell或执行交互式命令。
3. 容器日志和执行指令
docker logs <容器ID>:查看容器的日志输出。
docker exec -it <容器ID> <命令>:在运行中的容器中执行指定的命令。
docker attach <容器ID>:附加到正在运行的容器,查看其实时输出。
4. 网络和数据卷指令
docker network create <网络名称>:创建一个用户定义的网络。
docker network ls:列出所有网络。
docker volume create <卷名称>:创建一个数据卷。
docker volume ls:列出所有数据卷。
5. 其他指令
docker-compose up:使用Docker Compose启动多个容器。
docker-compose down:停止并删除Docker Compose定义的所有容器。
这些指令只是Docker功能的一小部分。Docker提供了更多的命令和选项,用于管理和操作容器、镜像、网络和卷等。您可以通过运行docker --help或查阅Docker官方文档来获取更详细的信息和指导。