Docker — 从入门到实践
Docker 在前端开发中的应用实例
1、使用 Docker 搭建 WordPress
2、Docker + Nginx 部署 React 应用
3、Docker 部署 node 服务
一、Docker 是什么?
了解 Docker 之前,我们先来了解一下虚拟机(Virtual Machine,简称 VM)。
1、什么是虚拟机?
为什么要用虚拟机?
整合资源是使用虚拟机的首要原因和目的。大多数的操作系统和应用,在安装(部署)时,都只会占用很少的硬件资源。
我们可以将虚拟机视为一种由软件组成的计算机,可以使用它来运行在真实物理计算机上运行的任何软件。通过将一台物理服务器划分为多个虚拟机并共享资源,可以更有效地利用硬件资源,提高资源利用率。
与物理机一样,虚拟机拥有自己的操作系统(Windows、Linux 等)、存储、网络、设置和软件等,并且与在该主机上运行的其他虚拟机完全隔离。
虚拟机可以做什么,有什么好处?
- 资源利用率最大化:虚拟机允许在一台物理服务器上同时运行多个虚拟机,从而提高服务器资源的利用率。通过共享处理器、内存和存储等资源,可以更有效地利用硬件资源,降低成本。
- 灵活性和可扩展性:虚拟机可以根据需要动态分配和调整资源。当应用程序的需求发生变化时,可以根据需要增加或减少虚拟机的数量和资源配额,实现灵活的资源管理和弹性扩展。
- 硬件无关性:虚拟机屏蔽了底层物理硬件的差异,使得应用程序可以独立于具体的硬件平台运行。这意味着应用程序可以在不同的操作系统和硬件环境中无缝迁移,提供更好的可移植性和兼容性。
- 隔离性和安全性:虚拟机提供了隔离的运行环境,不同的应用程序或服务可以在各自独立的虚拟机中运行,互不干扰。这种隔离性可以有效防止应用程序之间的冲突或故障相互影响,提高系统的稳定性和安全性。
- 简化管理和维护:通过虚拟化技术,可以统一管理和监控多个虚拟机,简化系统管理和维护的工作量。可以通过集中管理工具对虚拟机进行统一的配置、备份、恢复和升级,提高管理效率。
- 开发和测试环境:虚拟机可以用于创建和管理开发和测试环境,使开发人员可以在独立的虚拟机中进行应用程序的开发、测试和调试。这种隔离的开发和测试环境可以有效地模拟真实生产环境,并提供更好的可控性和可重现性。
2、什么是 Docker?
容器化:容器化是软件开发的一种方法,可以将应用程序和它所依赖的组件、相关的环境变量配置文件等打包成容器,然后在不同的环境中运行。容器技术最流行的实现就是 Docker。Docker 是一种虚拟化容器技术,其设计理念是 "一次构建,到处运行"。
举个例子:这就像码头上的集装箱运载货物一样,把货物(应用程序)打包后放在一个集装箱里,假设这个货物是一箱罐头和食用所需的勺子与叉子(依赖环境),通过货轮,货物可以很方便地从一个码头(假设是 Ubuntu 环境)运送到另一个码头(假设是 Centos环境)。在运输期间,货物不会受到任何的损坏(程序文件没有丢失和损坏),所以在另一个码头卸货后,依然可以很好地食用(正常启动)。
3、Docker VS 虚拟机
与虚拟机相比,Docker 以一种轻量级的方式实现了运行空间的隔离。虚拟机拥有的好处,Docker 也都有。打个比方,如果物理机是一栋写字楼,那么虚拟机就是写字楼当中不同的租赁单位(公司),而 Docker 就是办公室内的隔断或分区。
从这个比喻中,可以发现 Docker 和虚拟机之间的一些区别:
- 资源占用:虚拟机是独立的单位,每个虚拟机都需要分配独立的资源,包括处理器、内存和存储空间等,就像不同单位的行政、IT 支持部门;而容器则是办公室内的隔断或分区,它们共享相同的办公室资源,包括操作系统内核和环境配置等,就像同一单位共享同一个 IT 支持部门。因此,相比于虚拟机,容器的资源占用更少,可以更高效地利用硬件资源。
- 启动时间和性能:由于每个虚拟机需要启动整个操作系统,所以虚拟机的启动时间相对较长。而容器启动速度快,通常在几秒钟内完成启动,因为它们共享主机操作系统内核,不需要启动整个操作系统。虚拟机在性能方面可能会有一定的开销,而容器的性能开销较小,更接近于直接在主机上运行应用程序。
- 隔离性:虚拟机提供了较强的隔离性,每个虚拟机都运行在独立的操作系统实例中,相互之间隔离,互不影响。而容器是在操作系统层面进行隔离的,它们共享相同的操作系统内核,但通过容器技术实现了进程级别的隔离,使得容器之间相互独立。虚拟机提供了更强的隔离性,适合于需要更高安全性和完全隔离的场景,而容器提供了较轻量级的隔离性,适合于快速启动和高密度部署的场景。
- 管理和部署:虚拟机的管理和部署相对复杂,需要涉及虚拟机的配置、网络设置等。而容器具有更简化的部署和管理方式,通过使用容器镜像和容器编排工具,可以快速构建、部署和管理容器化应用。
- 操作系统支持:容器作为一种隔断,不能基于一种内核(Linux)提供另一种内核(Windows)的虚拟化运行环境,所以,基于 Linux 的 Docker 是不支持运行 Windows 应用的(Docker 可以在 Windows 运行,Docker 利用了 Windows 操作系统中的 Hyper-V 虚拟化技术来创建一个轻量级的 Linux 虚拟机,Docker 容器在这个 Linux 虚拟机中运行)。
二、Docker 介绍
1、基本概念
1.1、镜像
Docker 会把应用程序及依赖打包进镜像(Images)里,提供了容器运行时所需的程序、库、配置文件等,同时还包含了一些为容器运行时准备的一些配置参数(如 volume 卷)。
2.1、容器
容器(Container)是镜像的可运行实例。一个 Docker 镜像可以创建多个容器,镜像和容器的关系,就像是 JavaScript 中的类和实例的关系一样。
我们可以使用 Docker 提供的 API 创建、启动、停止、删除容器。在默认情况下,容器与容器、容器与主机在默认情况下是隔离的,拥有自己的独立进程空间、网络配置等。
容器由其镜像以及在创建或者启动容器时提供的配置选项来定义,当容器被删除时,未对容器状态做持久化存储的更改都会消失。
3.1、仓库
镜像构建完成后,可以很容易地在当前宿主机上运行。但是,如果需要在其它服务器上使用这个镜像,就需要一个集中的存储、分发镜像的服务,Docker Hub 就是这样的服务,叫作仓库( Docker Registry )。仓库的概念与 Git 类似,可以理解为 GitHub 这样的托管服务。
一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。
2、安装 Docker
参考官网:docs.docker.com/get-docker/
验证安装:
$ docker --version
输出所安装的 Docker 版本。
3、使用镜像
3.1、获取镜像
Docker Hub 上有大量的镜像可以用。从 Docker 镜像仓库获取镜像的命令是 docker pull:
$ docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
- 拉取一个镜像,需要指定 Docker Registry 的地址和端口号,默认是 Docker Hub;
- 还需要指定仓库名和标签,仓库名和标签可以唯一确定一个镜像,仓库名则由作者名和软件名组成;
- 而标签是可以省略的,如果省略,则默认使用 latest 作为标签名。
以拉取 centos 镜像为例:
$ docker pull centos
因为省略作者名,则作者名为 library,表示使用 Docker 官方的镜像,所以上面的命令等同于:
$ docker pull library/centos:latest
3.2、运行镜像
使用 docker run 命令,可以通过镜像创建一个容器:
$ docker run -it centos /bin/bash
3.3、列出镜像
要想列出已经下载下来的镜像,可以使用 docker image ls 命令或者快捷命令 docker images 。
$ docker images
列出部分镜像
假设我们拉取了很多镜像,现在想列出 java 仓库中的镜像,可以使用命令:
$ docker images java
也可以添加标签做进一步的过滤:
$ docker images java:8
3.4、删除本地镜像
当本地有些镜像我们不再需要时,那我们也可以删除该镜像,以节省存储空间。不过要注意,如果有使用该镜像创建的容器未删除,则不允许删除镜像。
$ docker image rm image_name/image_id
image_name 表示镜像名,image_id 表示镜像 id 。
快捷命令为:
$ docker rmi image_name/image_id
如果想要删除全部镜像:
$ docker rmi $(docker images -q)
强制删除全部镜像:
$ docker rmi -f $(docker images -q)
4、操作容器
4.1、启动容器
启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态的容器重新启动。
新建并启动:
$ docker run hello-world
如果本地环境中没有 hello-world 镜像,会从远程仓库中拉取镜像;如果有,则使用本地的镜像。
注意:命令执行之后, hello-world 这个容器会自动停止,因为该镜像的目的就是输出一段内容就结束了,但并不是所有的容器运行之后都会停止的。如果需要容器在后台持续运行,可以加一个 -d 参数,-d 参数的作用是在后台运行容器并打印容器 ID。
重新启动停止的容器:
$ docker start CONTAINER_ID
4.2、查看容器
如果要查看本地的容器,可以使用 docker container ls 命令:
$ docker container ls
查看所有容器也有简洁的写法,如下:
$ docker ps
注意,上面两条命令都是列出正在运行的容器。如果想要列出所有容器(包含已停止的容器),可以使用命令:
$ docker ps --all
或者快捷命令:
$ docker ps -a
4.3、停止容器
可以使用 docker container stop 来终止一个运行中的容器。也可以使用快捷命令:
$ docker stop container_id
此外,docker container restart 命令会将一个运行态的容器终止,然后再重新启动它。
4.4、删除容器
我们可以使用 docker container rm 命令,或者简洁的写法 docker rm 命令来删除容器。不过不允许删除正在运行的容器,因此如果要删除的话,就必须先停止容器。
$ docker rm container_id
container_id 表示容器 id,可以通过 docker ps 查看容器 id 。
当我们需要批量删除所有容器,可以用下面的命令:
$ docker rm $(docker ps -aq)
删除所有退出的容器:
$ docker container prune
4.5、进入容器
$ docker exec -it container_id command
container_id 表示容器的 id,command 表示 linux 命令,如 /bin/bash。
下面的命令则启动一个 bash 终端,允许用户进行交互:
$ docker run -it ubuntu:18.04 /bin/bash
root@75053a970732:/#
其中,-t 选项让 Docker 分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i 则让容器的标准输入保持打开。可以合起来写成 -it 。
在交互模式下,可以通过所创建的终端来输入命令,例如:
root@75053a970732:/# ls
bin dev home lib64 mnt proc run srv tmp var
boot etc lib media opt root sbin sys usr
三、构建镜像
1、使用 Dockerfile 定制镜像
Dockerfile 是一个 Docker 镜像的描述文件,由一行行构建镜像时所需的指令和说明构成。
下面以一个定制 nginx 镜像为例。
在一个空白目录中,建立一个文本文件,并命名为 Dockerfile:
$ mkdir mynginx
$ cd mynginx
$ touch Dockerfile
Dockerfile 的内容为:
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
FROM:定制的镜像都是基于 FROM 的镜像,这里的 nginx 就是定制需要的基础镜像,后续的操作都是基于 nginx; RUN:用于执行后面跟着的命令行命令。
1.1、build 命令
docker build 命令用于构建镜像,其格式为:
$ docker build -t [镜像的名字及标签,通常 name:tag] -f [指定要使用的 Dockerfile 路径] [ContextPath]
- -t 是 --tag 的简写,用于指定镜像的名字及标签;
- -f,指定要使用的 Dockerfile 路径;
- Context(上下文), 当我们构建镜像的时候,经常会需要将一些本地文件复制进镜像(比如通过 COPY 指令、 ADD 指令等)。而使用 docker build 命令构建镜像时,并不是在本机环境中完成的,而是在所安装的 Docker 引擎中完成的。所以这个时候,Docker 引擎无法用到我们本机的文件,就需要把我们本机的指定目录下的内容一起打包(上下文包)提供给 Docker 引擎用来构建镜像。
1.3、构建镜像并启动容器
下面使用在 Dockerfile 文件所在目录执行:
# 镜像名称为 nginx:test, 上下文为根目录
$ docker build -t nginx:test .
启动容器:
$ docker run -p 8088:80 -d nginx:test
- -p:指定端口映射,将 nginx 默认的 80 端口映射到主机的 8088 端口;
- -d:后台运行容器,并返回容器 ID。
浏览器访问 http://localhost:8088/ 查看输入内容。
四、编排容器
前面介绍的 Dockerfile,它可以用于构建一个独立的镜像。而如果涉及多个容器的组合运行(如一个 web 项目,除了 web 服务容器本身,还需要加上数据库服务容器等等),就可以通过 docker-compose 来实现编排了。
1、使用 docker-compose
有三个步骤:
- 使用 Dokcerfile 定制所需的镜像(如果不需要定制,可不需要这一步);
- 编写 docker-compose.yml 文件,定义构建应用程序的服务;
- 执行 docker-compose up 命令来启动并运行整个应用程序。
常用参数:
- docker-compose up -d,在后台运行;
- docker-compose ps,列出项目中所有的容器
- docker-compose stop,停止正在运行的容器,可以通过docker-compose start 再次启动
- docker-compose build,构建(重新构建)项目中的服务容器
- docker-compose down,停止和删除容器、网络、卷、镜像。
五、场景实践
1、使用 Docker 搭建 WordPress
WordPress 是一个基于 Mysql 和 PHP 的博客应用程序。使用 Docker 来搭建 WordPress,需要两个镜像:Mysql 和 WordPress。
下面使用 docker-compose 来编排这两个镜像:
version: '3.8'
services:
db:
image: mysql:5.7
# 持久化数据 HOST:CONTAINER 格式
volumes:
- ./db_data:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: your_mysql_root_password
MYSQL_DATABASE: your_mysql_database
MYSQL_USER: your_mysql_user
MYSQL_PASSWORD: your_mysql_password
wordpress:
image: wordpress:latest
volumes:
- ./wp_data:/var/www/html
ports:
- 8000:80
restart: always
depends_on:
- db
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: your_mysql_user
WORDPRESS_DB_PASSWORD: your_mysql_password
WORDPRESS_DB_NAME: your_mysql_database
# 定义 Docker 卷的集合
volumes:
db_data:
wp_data:
在 Docker 中,volumes(卷)是一种用于持久化存储数据的机制。它允许你在容器和宿主机之间共享和保留数据。当你在 Docker 中创建或使用一个卷时,你可以将它挂载到容器的特定路径上,使得容器内的数据可以存储在该卷中。这样做的好处是,即使容器被删除或重新创建,卷中的数据仍然保持不变,不会丢失。这对于持久化存储数据、在容器之间共享数据或与宿主机进行数据交互非常有用。
使用 docker-compose 命令启动容器:
$ docker-compose up -d
2、Docker + Nginx 部署 React 应用
编写 Dockerfile 文件:
# node 镜像
# apline 版本的 node 镜像会小很多
FROM node:16-alpine3.16 as build-stage
# 在容器中创建目录
RUN mkdir -p /app
# 指定工作空间,后面的指令都会在当前目录下执行
WORKDIR /app
# 解释说明:
# 1、Docker 镜像是分层的。Dockerfile 中的每个指令都会创建一个新的镜像层,每个 RUN 都是一个指令
# 2、当 Dockerfile 的指令修改了,或者复制的文件变化了,或者构建镜像时指定的变量变化了,对应的镜像层缓存就会失效
# 3、而某一层的镜像缓存失效之后,它之后的镜像层缓存都会失效
# 4、镜像层是不可变的,如果我们在某一层中添加一个文件,然后在下一层中删除它,则镜像中依然会包含该文件(只是这个文件在 Docker 容器中不可见了)。
# 5、所以我们先拷贝 package.json,然后 RUN npm i 安装依赖,形成一个镜像层,再拷贝其他所有文件,形成一个镜像层;
# 之后如果代码有所变动,但是 package.json 没有变动,再次执行时,就不会再安装依赖了,可以节省很多时间。
# package.json 有变动,才会重新执行 RUN npm i 安装依赖。
# 拷贝 package.json、package-lock.json 或者 yarn.lock
COPY package.json *.lock ./
# 设置 npm 源
RUN npm config set registry https://registry.npmmirror.com
# 安装依赖
RUN npm install
# 拷贝其他所有文件到容器(除了 .dockerignore 中的目录和文件)
COPY . .
# build 项目
RUN npm run build
# nginx 镜像
FROM nginx:alpine
# 复制 build 后的文件到 nginx 的指定目录(/usr/share/nginx/html)
COPY --from=build-stage /app/build/ /usr/share/nginx/html
# 用本地的 default.conf 配置来替换 nginx 镜像里的默认配置
COPY nginx/nginx.conf /etc/nginx/nginx.conf
# 暴露 80 端口
EXPOSE 80
# 运行容器时执行命令
# 容器启动时执行的那条入口命令一旦结束了,容器也会结束。所以需要将守护关闭,让 nginx 后台运行。
CMD ["nginx", "-g", "daemon off;"]
使用 Dockerfile 构建
- 构建镜像
$ docker build -t react_nginx:1.0 .
- 根据镜像创建容器
$ docker run -p 3000:80 react_nginx:1.0
也可以在 docker-compose 中使用这个 Dockerfile 文件进行构建:
version: '3'
services:
web:
# 指定 build 配置,使其自动构建镜像并启动容器
build:
context: ./
dockerfile: ./Dockerfile
image: react_nginx:1.0
ports:
- 3000:80
container_name: react_nginx_test
运行:
$ docker-compose up -d --build
3、Docker 部署 node 服务
部署一个 node + nest.js + mysql 的应用服务,需要使用 node 和 mysql 两个基础镜像。
编写 Dockerfile:
FROM node:16-alpine3.16 as build-stage
# 指定工作空间,后面的指令都会在当前目录下执行
WORKDIR /usr/src/app
COPY package.json *.lock ./
RUN npm config set registry https://registry.npmmirror.com
RUN yarn install
COPY . /usr/src/app
RUN yarn build
EXPOSE 7001
CMD ["node", "dist/main.js"]
使用 docker-compose 编排镜像:
version: '3.9'
services:
database:
platform: linux/x86_64
image: mysql:5.7
restart: always
container_name: db
ports:
- '3306:3306'
volumes:
- ./data/mysql:/var/lib/mysql
- ./doc/mysql-init/:/docker-entrypoint-initdb.d
networks:
- nesjs-network
env_file:
- .env
server:
image: node:current-alpine3.11
container_name: nestjs
build:
context: .
dockerfile: Dockerfile
ports:
- '7001:7001'
depends_on:
- database
networks:
- nesjs-network
restart: always
links:
- database
env_file:
- .env
volumes:
- .:/app
- /app/node_modules
command: npm run start:prod
networks:
nesjs-network:
driver: bridge
name: nesjs-network
运行:
$ docker-compose up -d --build
在 postman 中调试接口:
六、代码仓库
七、参考
转载自:https://juejin.cn/post/7239875883254726713