likes
comments
collection
share

Docker — 从入门到实践

作者站长头像
站长
· 阅读数 28

Docker 在前端开发中的应用实例

1、使用 Docker 搭建 WordPress

2、Docker + Nginx 部署 React 应用

3、Docker 部署 node 服务

一、Docker 是什么?

了解 Docker 之前,我们先来了解一下虚拟机(Virtual Machine,简称 VM)。

1、什么是虚拟机?

为什么要用虚拟机?

整合资源是使用虚拟机的首要原因和目的。大多数的操作系统和应用,在安装(部署)时,都只会占用很少的硬件资源。

我们可以将虚拟机视为一种由软件组成的计算机,可以使用它来运行在真实物理计算机上运行的任何软件。通过将一台物理服务器划分为多个虚拟机并共享资源,可以更有效地利用硬件资源,提高资源利用率

与物理机一样,虚拟机拥有自己的操作系统(Windows、Linux 等)、存储、网络、设置和软件等,并且与在该主机上运行的其他虚拟机完全隔离。

Docker — 从入门到实践

虚拟机可以做什么,有什么好处?

  1. 资源利用率最大化:虚拟机允许在一台物理服务器上同时运行多个虚拟机,从而提高服务器资源的利用率。通过共享处理器、内存和存储等资源,可以更有效地利用硬件资源,降低成本。
  2. 灵活性和可扩展性:虚拟机可以根据需要动态分配和调整资源。当应用程序的需求发生变化时,可以根据需要增加或减少虚拟机的数量和资源配额,实现灵活的资源管理和弹性扩展。
  3. 硬件无关性:虚拟机屏蔽了底层物理硬件的差异,使得应用程序可以独立于具体的硬件平台运行。这意味着应用程序可以在不同的操作系统和硬件环境中无缝迁移,提供更好的可移植性和兼容性。
  4. 隔离性和安全性:虚拟机提供了隔离的运行环境,不同的应用程序或服务可以在各自独立的虚拟机中运行,互不干扰。这种隔离性可以有效防止应用程序之间的冲突或故障相互影响,提高系统的稳定性和安全性。
  5. 简化管理和维护:通过虚拟化技术,可以统一管理和监控多个虚拟机,简化系统管理和维护的工作量。可以通过集中管理工具对虚拟机进行统一的配置、备份、恢复和升级,提高管理效率。
  6. 开发和测试环境:虚拟机可以用于创建和管理开发和测试环境,使开发人员可以在独立的虚拟机中进行应用程序的开发、测试和调试。这种隔离的开发和测试环境可以有效地模拟真实生产环境,并提供更好的可控性和可重现性。

2、什么是 Docker?

容器化:容器化是软件开发的一种方法,可以将应用程序和它所依赖的组件、相关的环境变量配置文件等打包成容器,然后在不同的环境中运行。容器技术最流行的实现就是 Docker。Docker 是一种虚拟化容器技术,其设计理念是 "一次构建,到处运行"。

Docker — 从入门到实践

举个例子:这就像码头上的集装箱运载货物一样,把货物(应用程序)打包后放在一个集装箱里,假设这个货物是一箱罐头和食用所需的勺子与叉子(依赖环境),通过货轮,货物可以很方便地从一个码头(假设是 Ubuntu 环境)运送到另一个码头(假设是 Centos环境)。在运输期间,货物不会受到任何的损坏(程序文件没有丢失和损坏),所以在另一个码头卸货后,依然可以很好地食用(正常启动)。

Docker — 从入门到实践

3、Docker VS 虚拟机

与虚拟机相比,Docker 以一种轻量级的方式实现了运行空间的隔离。虚拟机拥有的好处,Docker 也都有。打个比方,如果物理机是一栋写字楼,那么虚拟机就是写字楼当中不同的租赁单位(公司),而 Docker 就是办公室内的隔断或分区

从这个比喻中,可以发现 Docker 和虚拟机之间的一些区别:

  • 资源占用:虚拟机是独立的单位,每个虚拟机都需要分配独立的资源,包括处理器、内存和存储空间等,就像不同单位的行政、IT 支持部门;而容器则是办公室内的隔断或分区,它们共享相同的办公室资源,包括操作系统内核和环境配置等,就像同一单位共享同一个 IT 支持部门。因此,相比于虚拟机,容器的资源占用更少,可以更高效地利用硬件资源。
  • 启动时间和性能:由于每个虚拟机需要启动整个操作系统,所以虚拟机的启动时间相对较长。而容器启动速度快,通常在几秒钟内完成启动,因为它们共享主机操作系统内核,不需要启动整个操作系统。虚拟机在性能方面可能会有一定的开销,而容器的性能开销较小,更接近于直接在主机上运行应用程序。
  • 隔离性:虚拟机提供了较强的隔离性,每个虚拟机都运行在独立的操作系统实例中,相互之间隔离,互不影响。而容器是在操作系统层面进行隔离的,它们共享相同的操作系统内核,但通过容器技术实现了进程级别的隔离,使得容器之间相互独立。虚拟机提供了更强的隔离性,适合于需要更高安全性和完全隔离的场景,而容器提供了较轻量级的隔离性,适合于快速启动和高密度部署的场景。
  • 管理和部署:虚拟机的管理和部署相对复杂,需要涉及虚拟机的配置、网络设置等。而容器具有更简化的部署和管理方式,通过使用容器镜像和容器编排工具,可以快速构建、部署和管理容器化应用。
  • 操作系统支持:容器作为一种隔断,不能基于一种内核(Linux)提供另一种内核(Windows)的虚拟化运行环境,所以,基于 Linux 的 Docker 是不支持运行 Windows 应用的(Docker 可以在 Windows 运行,Docker 利用了 Windows 操作系统中的 Hyper-V 虚拟化技术来创建一个轻量级的 Linux 虚拟机,Docker 容器在这个 Linux 虚拟机中运行)。

Docker — 从入门到实践

二、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 镜像的描述文件,由一行行构建镜像时所需的指令和说明构成。

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,需要两个镜像:MysqlWordPress

下面使用 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 中调试接口:

Docker — 从入门到实践

六、代码仓库

七、参考

转载自:https://juejin.cn/post/7239875883254726713
评论
请登录