下载安装
最简单的学习docker的方式可能是docker desktop
配合 wsl 2
。下载Docker Desktop
这个软件后所有命令就都能在wsl
上使用了。
因为之后需要在Linux服务器上进行部署,所以也不能单纯在docker desktop
上玩,故下面的命令都是在wsl上跑。
# 打开powershell,查看自己的wsl版本
wsl --status
# 以root用户身份进入到wsl
wsl -u root
# 以root用户身份进入到wsl,并且直接进入到root目录下
wsl ~ -u root
参考资料:
www.docker.com/
yeasy.gitbook.io/docker_prac…
www.bilibili.com/video/BV1CJ…
微软官方的WSL基本命令
Docker常见命令
镜像命令
查看镜像
docker images
docker images -q #查看所有的镜像id
搜索镜像
docker search 镜像名称
拉取镜像:从Docker仓库下载镜像到本地,镜像名称格式为 名称:版本号
,如果版本号不指定则是最新的版本。如果不知道镜像版本,可以去dockerhub 搜索对应镜像查看。
容器相关
查看容器:
docker ps
是一个 Docker 命令,用于列出当前正在运行的容器。
ps
是 "Process Status" 的缩写,docker ps
命令的作用是显示 Docker 主机上正在运行的容器的状态信息,包括容器的 ID、名称、使用的镜像、运行状态、端口映射等。
当你执行 docker ps
命令时,Docker 将返回一个表格,其中包含当前正在运行的容器的详细信息。默认情况下,docker ps
仅显示正在运行的容器,不包括已停止的容器。
如果你希望查看所有容器(包括已停止的容器),可以使用 docker ps -a
命令。
创建容器/运行容器:
在Docker中,docker run
和docker create
是两个常用的命令,用于创建和运行容器。它们之间的区别如下:
创建容器 vs 运行容器:
docker create
:该命令用于创建一个新的容器,但并不立即运行它。创建容器后,可以使用其他命令如docker start
来启动该容器。创建容器时,可以指定容器的配置选项,但不会自动执行容器的命令。docker run
:该命令用于创建并运行一个新的容器。它是docker create
和docker start
两个步骤的组合。运行容器时,可以指定容器的配置选项,并在容器启动后立即执行容器的命令。
容器生命周期:
docker create
:创建容器后,容器将进入已创建(created)状态,并处于停止状态。需要使用docker start
命令来启动容器,使其进入运行状态。docker run
:创建并运行容器后,容器将进入已运行(running)状态,并立即开始执行容器的命令。
docker create
用于创建容器但不立即运行它,而docker run
用于创建并运行容器。
docker run
- -i:保持容器运行。通过与-t一起同时使用,使用
-it
创建容器后,会自动进入到容器中,退出容器后,容器会自动关闭。 - -t:为容器分配一个伪终端
-d
用于在后台(detached mode)运行容器,需要使用docker exec进入容器,退出后,容器不会关闭-it
创建的容器一般称之为交互式容器,-id创建的容器一般称为守护式容器--name
为创建的容器命名,如--name=c1
名字叫c1
docker run 镜像名:版本
docker run -id --name=c1 centos
进入容器:
docker exec -it 容器名 命令
# 例
docker exec -it c1 /bin/bash
其它命令
启动容器:
docker start 容器名/container id
删除容器:
docker rm 容器名/container id
# 查看正在运行的容器的id (查看所有容器的id docker ps -aq)
docker ps -q
# 删除所有容器
docker rm `docker ps -aq`
查看容器的信息
docker inspect 容器名/container id
重启容器:
docker restart 容器名/container id
容器的数据卷
概念
在Docker中,数据卷(Data Volumes)是一种用于持久化存储数据的机制。数据卷可以在容器和主机之间共享和重用,使得容器中的数据在容器销毁或重启后仍然可用。
数据卷提供了一种方便的方式来处理容器中的持久化数据。它们可以用于存储数据库文件、配置文件、日志文件以及其他需要持久化的应用数据。
数据卷有以下特点:
数据卷可以通过两种方式在容器中使用:
当创建数据卷时,可以选择使用命名卷(Named Volumes)或挂载主机目录(Bind Mounts)这两种方式。下面我将分别介绍它们的创建方式和特点。
挂载主机目录(Bind Mounts)
- 创建挂载主机目录:指定主机文件系统中的目录作为挂载点。例如:
/path/on/host
- 在容器中使用挂载主机目录:在运行容器时,使用
-v
或--mount
参数将主机目录与容器中的路径关联起来。例如:docker run -v /path/on/host:/path/to/container
或docker run --mount type=bind,source=/path/on/host,target=/path/to/container
- 特点:
- 挂载主机目录将主机的特定目录直接映射到容器中,容器可以直接读取和写入主机文件系统中的数据。
- 挂载主机目录提供了更高的灵活性,可以使用主机上的任意目录作为数据卷。
- 挂载主机目录需要手动创建和配置主机目录,主机上的数据变动会直接影响容器中的数据。
在挂载的本机目录下创建了文件,在容器上也能看到该文件。
# 创建容器 将本机的目录挂载到docker容器上,如果目录不存在会自动被创建
docker run -it --name=c1 -v /root/data:/root/data_container centos:latest
# 一个容器挂载多个目录 表示换行
docker run -it --name=c2 -v /root/data:/root/containerData1
-v /root/data2:/root/containerData2 centos:latest
本机:
容器:
同样,在容器上对挂载目录做出修改,在主机上也能看到变化:
容器:
主机:
命名卷(Named Volumes)
- 创建命名卷:使用
docker volume create
命令创建命名卷,并为其指定一个名称。例如:docker volume create myvolume
- 在容器中使用命名卷:在运行容器时,使用
-v
或--mount
参数将命名卷与容器中的路径关联起来。例如:docker run -v myvolume:/path/to/container
或docker run --mount source=myvolume,target=/path/to/container
- 特点:
- Docker引擎负责命名卷的创建和管理,无需手动创建和配置主机目录。
- 命名卷的数据存储在主机的文件系统中的特定目录中,可以在不同的容器之间共享和重用。
- 命名卷的生命周期与容器的生命周期相互独立,即使容器被删除,命名卷中的数据仍然存在。
查看数据卷的具体信息
在主机里使用以下命令可以查看容器的信息:
docker inspect 容器名/容器id
挂载主机的目录配置在"mount"下面:
{
"Mounts": [
{
"Type": "bind",
"Source": "/root/data",
"Destination": "/root/containerData1",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
},
{
"Type": "bind",
"Source": "/root/data2",
"Destination": "/root/containerData2",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
]
}
多容器间的数据交换
挂载到同一个数据卷
可以通过把本机中同一个数据卷挂载到2个不同容器中,就可以间接
实现数据交换了:
docker run -it --name=c1 -v ~/data:/root/c1Data centos
docker run -it --name=c2 -v ~/data:/root/c2Data centos
数据卷容器
数据卷容器(Data Volume Container)是本身是一个特殊的容器,专门用于创建和管理数据卷,并与其他容器共享数据。
创建数据卷容器:可以使用 docker create
来创建一个普通的容器:
docker create --name=dataVolumeContainer -v /容器目录 centos
其他容器(必须使用run创建)使用--volumes-from
来挂载该数据卷容器,且不管该数据卷容器是否在运行。
docker run it --name=c4 --volumes-from dataVolumeContainer centos
Docker应用部署
- 容器内的网络服务和外部的机器不能通信,但外部机器内和宿主机可直接通信,且宿主机和容器可以直接通信
- 当容器中的网络服务需要被外部机器访问时,可以将容器中提供服务的端口映射到宿主机的端口上。外部机器访问宿主机的该端口,从而间接访问容器的服务,这种操作称为端口映射
MySQL部署
在docker容器容器中部署MySQL,并通过外部的MySQL客户端来操作MySQL Server
docker search mysql
docker pull mysql
# 在/root目录下创建mysql目录用于存储mysql数据信息
mkdir ~/mysql
cd ~/mysql
docker run -id
-p 3307:3306
--name=c_mysql
-v $PWD/conf:/etc/mysql/conf.d
-v $PWD/logs:/logs
-v $PWD/data:/var/lib/mysql
-e MYSQL_ROOT_PASSWORD=123456
mysql
- 参数说明:
- -p 3307:3306:将容器的 3306 端口映射到宿主机的 3307 端口。
- -v $PWD/conf:/etc/mysql/conf.d:将主机当前目录下的 conf/my.cnf 挂载到容器的 /etc/mysql/my.cnf。配置目录
- -v $PWD/logs:/logs:将主机当前目录下的 logs 目录挂载到容器的 /logs。日志目录
- -v $PWD/data:/var/lib/mysql :将主机当前目录下的data目录挂载到容器的 /var/lib/mysql 。数据目录
- **-e MYSQL_ROOT_PASSWORD=123456:**初始化 root 用户的密码。
一般容器的端口映射到主机时,2者的端口会写得一样,这里为了强调是3307是主机,写在冒号前,3306是容器,写在冒号后,所以写得不一样。
接着,我们可以进入到mysql:
docker exec -it c_mysql /bin/bash
然后,输入密码登录mysql server:
mysql -u root -p
Tomcat部署
docker search tomcat
docker pull tomcat
# 在/root目录下创建tomcat目录用于存储tomcat数据信息
mkdir ~/tomcat
cd ~/tomcat
docker run -id --name=c_tomcat
-p 8080:8080
-v $PWD:/usr/local/tomcat/webapps
tomcat
- 参数说明:
-
**-p 8080:8080:**将容器的8080端口映射到主机的8080端口
**-v $PWD:/usr/local/tomcat/webapps:**将主机中当前目录挂载到容器的webapps
-
Nginx部署
docker search nginx
docker pull nginx
# 在/root目录下创建nginx目录用于存储nginx数据信息
mkdir ~/nginx
cd ~/nginx
mkdir conf
cd conf
# 在~/nginx/conf/下创建nginx.conf文件,粘贴下面内容
vim nginx.conf
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
docker run -id --name=c_nginx
-p 80:80
-v $PWD/conf/nginx.conf:/etc/nginx/nginx.conf
-v $PWD/logs:/var/log/nginx
-v $PWD/html:/usr/share/nginx/html
nginx
- 参数说明:
- -p 80:80:将容器的 80端口映射到宿主机的 80 端口。
- -v $PWD/conf/nginx.conf:/etc/nginx/nginx.conf:将主机当前目录下的 /conf/nginx.conf 挂载到容器的 :/etc/nginx/nginx.conf。配置目录
- -v $PWD/logs:/var/log/nginx:将主机当前目录下的 logs 目录挂载到容器的/var/log/nginx。日志目录
index.html
,然后访问ip:80
就能访问运行在容器的页面了。部署Redis
docker search redis
docker pull redis
docker run -id --name=c_redis -p 6379:6379 redis
./redis-cli.exe -h 192.168.149.135 -p 6379
Dockerfile
Docker镜像原理
思考:
Docker 镜像是一种轻量级、独立的可执行软件包,其中包含了运行特定应用程序所需的一切:代码、运行时环境、系统工具、系统库等。可以将镜像看作是一个只读模板,用于创建 Docker 容器。镜像通过层(layers)的方式进行组织,每个层都包含了文件系统的一部分,多个层的叠加组合形成了完整的镜像。镜像的本质是一个可执行的文件,可以被分发、存储和加载到 Docker 引擎中。
Docker中一个centos镜像为什么只有200MB,而一个centos操作系统的iso文件要几个个G?
Docker 镜像与操作系统的 ISO 文件之间存在一些关键区别。ISO 文件是一个完整的操作系统安装映像,包含了操作系统的所有组件、库和工具,以及默认安装的应用程序和配置。它旨在提供一个完整的操作系统环境,以便用户可以在物理机器或虚拟机上进行安装。
相比之下,Docker 镜像是基于容器化技术的,它利用操作系统的内核并与宿主机共享操作系统资源。Docker 镜像通常采用分层存储的方式,每个层都只包含了应用程序或组件的变化部分,这种共享层的机制使得镜像可以高效地共享和重用。
当创建 CentOS 镜像时,Docker 可以基于一个基础镜像(例如官方提供的 CentOS 镜像)进行构建,然后在其上添加所需的应用程序和配置。这样,镜像中的每个层都只包含了变化的部分,而不需要重复存储操作系统本身,从而大大减小了镜像的大小。
Docker中一个tomcat镜像为什么有500MB,而一个tomcat安装包只有70多MB?
类似于 CentOS 镜像的情况,Docker 镜像中的 Tomcat 镜像也采用了分层存储的机制。Tomcat 安装包只包含了 Tomcat 服务器本身的二进制文件和所需的库文件,而 Docker 镜像中的 Tomcat 镜像除了包含 Tomcat 本身,还包含了一个基础操作系统镜像以及其他可能需要的依赖项。这些附加的层(如基础操作系统层、库文件层等)增加了镜像的大小。
Linux文件系统由 bootfs
和 rootfs
两部分组成:
- bootfs:包含
bootloader
(引导加载程序)和kernel
(内核) - rootfs: 是root文件系统,就是典型的Linux系统中的/dev,/proc,/bin,/etc等标准执行目录和程序
- 不同的linux发行版,bootfs基本相同,而rootfs不同(比如centos和ubuntu)
Docker镜像制作
Docker镜像如何制作?
容器转为镜像 (使用较少)
docker commit 容器id 镜像名称:版本号 # 根据容器生成镜像
docker save -o 压缩文件名称 镜像名称:版本号 # 压缩
# 加载
docker load -i 压缩文件名称
Dockerfile
Dockerfile
是用于构建Docker镜像的文本文件,它包含了一系列的指令和配置,用于定义镜像的构建过程和运行环境。
关键字 | 作用 | 备注 |
---|---|---|
FROM | 指定父镜像 | 指定 Dockerfile 基于哪个镜像构建 |
LABEL | 标签 | 标明 Dockerfile 的标签,可以在 Docker 镜像的基本信息中查看 |
RUN | 执行命令 | 执行一段命令,默认使用 /bin/sh 格式:RUN command 或者 RUN ["command"] |
CMD | 容器启动命令 | 启动容器时的默认命令,与 ENTRYPOINT 配合使用 |
COPY | 复制文件 | 在构建过程中将文件复制到镜像中 |
ADD | 添加文件 | 在构建过程中添加文件到镜像中,不仅限于当前构建上下文,可以来自远程服务 |
ENV | 环境变量 | 指定构建时的环境变量,可以在启动容器时使用 -e 覆盖 |
ARG | 构建参数 | 构建过程中使用的参数,只在构建时有效,如果有相同名字的 ENV,ENV 会覆盖 ARG |
VOLUME | 定义可挂载的数据卷 | 指定哪些目录在启动容器时可以挂载到文件系统中,使用 -v 绑定 |
EXPOSE | 暴露端口 | 定义容器运行时监听的端口,使用 -p 来绑定 |
WORKDIR | 工作目录 | 指定容器内部的工作目录,如果不存在则自动创建,可以使用绝对或相对路径 |
USER | 指定执行用户 | 指定在 RUN、CMD、ENTRYPOINT 执行命令时使用的用户 |
HEALTHCHECK | 健康检查 | 指定检测容器健康状态的命令,一般应用本身有健康检查机制,该指令用处不大 |
ONBUILD | 触发器 | 当基础镜像中存在 ONBUILD 关键字时,在执行 FROM 后会执行 ONBUILD 命令 |
STOPSIGNAL | 发送退出信号 | 设置发送给容器的系统调用信号以退出 |
SHELL | 指定执行脚本的 Shell | 指定在 RUN、CMD、ENTRYPOINT 执行命令时使用的 Shell |
生成镜像:
docker build -t .
# docker build -t myimage:1.0 .
# -t 设置标签
# . 读取当前目录下的dockerfile
# -f 设置此次生成镜像读取哪个路径下的dockerfile
DockerFile案例
案例一
自定义centos7镜像,要求:1. 默认登录后的路径是/usr 2. 能够使用vim命令
# 定义父镜像 set the base image
FROM centos:7
# 定义作者信息
LABEL maintainer="jiaqi"
# 执行安装vim 在 RUN yum install -y vim 指令中,运行 yum clean all 来清理 yum 缓存。
RUN yum install -y vim && yum clean all
# 定义默认的工作路径
WORKDIR /usr
# 定义容器启动执行的命令:
CMD ["/bin/bash"]
案例二
给一个Node.js
应用编写Dockerfile,这里我使用自己的一个小项目,可以参考这篇神光大佬的文章来写。
# 构建阶段
FROM node:18.17-alpine3.17 as build-stage
WORKDIR /server
COPY ./server/package.json ./server/package-lock.json ./
RUN npm install
COPY ./server .
RUN npm run build
ARG port=4000
# 生成最后的镜像
FROM node:18.17-alpine3.17 as production-stage
WORKDIR /server
COPY --from=build-stage ./server/dist ./
COPY ./server/package.json ./server/package-lock.json ./
RUN npm install --production
ENV PORT=$port
EXPOSE ${port}
CMD ["node", "app.js"]
我修改了几次后,就顺利跑通了,第一次看到自己项目跑到docker容器里面,内心多少有点小激动!
不过,这只是第一步,因为我的项目还有使用mongoDB
,Nginx
,需要使用Docker Compose
把它们一起跑起来😉。
服务编排
Docker Compose
Docker Compose
是一个编排多容器分布式部署的工具,提供命令集管理容器化应用的完整开发周期,包括服务构建启动和停止。使用步骤:
compose.yaml
定义组成应用各服务docker compose up
(docker compose v1 使用docker-compose up
命令)启动应用使用
可以在这里找到下载和安装方法。
我使用的是wsl2
+windows docker destop
来学习docker的,docker desktop已经帮我把安装这一步干好了,所以不需要在WSL中安装docker compose。
# 进入wsl ,查看自己是否已经安装好了
root@jiaqi:~# docker-compose -v
Docker Compose version v2.20.2-desktop.1
我这里想要实现的用compose串联起 Nginx,Node,MongoDB。
编排MongoDB和Node
事情得一步一步来进行,特别是对于我这样的新手来说。
第一步,我们先让MongoDB和Node能够一起运行。
基于以下几个要点:
- 先把数据库跑起来,然后再跑后端服务 (所以这里backend加了一个depends_on: mongo)
- 数据库和后端运行在同一网络下(这里将网络命名为mongo,并且分别指定了backend和mongo使用网络mongo)
- 因为我的后端服务代码中MONGO_URI是从环境变量中读取的,所以这里还设置了一下环境变量
services:
backend:
restart: always
build:
context: ./server
dockerfile: ./Dockerfile
environment:
- MONGO_URI=mongodb://mongo:27017
ports:
- 4000:4000
depends_on:
- mongo
stdin_open: true
networks:
- mongo
mongo:
image: mongo
restart: always
networks:
- mongo
expose:
- 27017
networks:
mongo:
services
:这是一个服务定义的部分,用于定义要运行的各个容器服务。backend
:这是一个名为backend
的服务定义,它是后端服务器的容器。
restart: always
:指定在容器退出时总是重新启动容器。build
:指定构建该服务所需的参数。context: ./server
:指定构建上下文的路径,即包含后端服务器代码的目录。dockerfile: ./Dockerfile
:指定用于构建该服务的Dockerfile的路径。
environment
:指定容器中的环境变量。MONGO_URI=mongodb://mongo:27017
:设置名为MONGO_URI
的环境变量,指定MongoDB的连接URI。
ports
:指定容器的端口映射。4000:4000
:将主机的4000端口映射到容器的4000端口,使得可以通过主机的4000端口访问后端服务。
depends_on
:指定该服务所依赖的其他服务。mongo
:该服务依赖于名为mongo
的服务,即MongoDB数据库服务。
stdin_open: true
:保持标准输入打开,以便可以与容器进行交互。networks
:指定该服务所连接的网络。mongo
:将该服务连接到名为mongo
的网络中。
mongo
:这是一个名为mongo
的服务定义,它是MongoDB数据库的容器。
image: mongo
:使用名为mongo
的Docker镜像作为容器基础。restart: always
:指定在容器退出时总是重新启动容器。networks
:指定该服务所连接的网络。mongo
:将该服务连接到名为mongo
的网络中。
expose
:指定容器所暴露的端口。27017
:暴露容器的27017端口,以允许其他容器连接到MongoDB数据库。
networks
:这是一个网络定义的部分,用于定义容器之间的网络。
mongo
:定义了一个名为mongo
的网络,该网络用于连接backend
和mongo
这两个服务。
总体而言,该Dockerfile定义了两个服务:一个后端服务和一个MongoDB数据库服务,它们分别运行在独立的容器中,并通过网络进行连接。后端服务在容器的4000端口提供服务,并通过环境变量指定了MongoDB的连接URI。MongoDB服务则暴露容器的27017端口,以供其他容器连接。
最后,将它们跑起来:
我的浏览器就能直接访问我的后端服务localhost:4000
增加Nginx
后端编排成功后,我还需要实现使用Nginx反向代理,从而为之后使用Let's encrypt
做好准备。我的Nignx配置需要实现:
- 反向代理4000端口
- 挂载sites-available目录,因为之后会使用certbot,certbot会修改
/etc/nginx/sites-available
目录
services:
backend:
restart: always
build:
context: ./server
dockerfile: ./Dockerfile
environment:
- MONGO_URI=mongodb://mongo:27017
ports:
- 4000:4000
depends_on:
- mongo
stdin_open: true
networks:
- mongo
mongo:
image: mongo
restart: always
networks:
- mongo
expose:
- 27017
ports:
- 27017:27017
nginx:
image: nginx:1.25.2-alpine
restart: always
ports:
- 80:80
- 443:443
volumes:
- /etc/nginx/sites-available/:/etc/nginx/sites-available/
- /etc/nginx/nginx.conf:/etc/nginx/nginx.conf
depends_on:
- backend
networks:
- mongo
networks:
mongo:
这里sites-avaliable文件交的Nignx配置可以见这篇文章。
server {
listen 80 ;
listen [::]:80 ;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
return 301 https://$host$request_uri;
}
server {
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
server_name example.org; # managed by Certbot
location / {
proxy_pass http://localhost:4000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/example.org/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.org/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
nginx.conf的配置如下:
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
把这些文件挂载好,之后再使用certbot
的命令获得免费的SSL认证。