一步一步从0到1掌握Docker

1,927 阅读14分钟

前言

近几年,基本上大多数互联网公司都已经使用Docker来进行项目打包上线了,因此学会Docker不言而喻是一个前端开发者也必须掌握的技能了.但是工作中我虽然用的多,但是掌握的也不够透彻,因此我花了半个多月的时间仔仔细细的梳理了一遍,我总结了工作中最常用的一些知识点,希望对大家有帮助,如有遗漏或者错误,欢迎大家来指正~~

(要是点个赞就更感激不尽了)

安装

curl -fsSL get.docker.com -o get-docker.sh
sh get-docker.sh

如果安装不上可以用这个

curl -sSL https://get.daocloud.io/docker | sh

启动docker

systemctl start docker

如果大家使用的编辑器是vscode可以安装一下Docker的vscode插件

image.png

Docker的基本操作

// 启动
systemctl start docker

// 开机默认启动
systemctl enable docker

// 查看版本
docker version

// 查看信息
docker info

// 查看用法
docker

容器

什么是容器?对于我自己的理解,可以认为在编程语言中对象的概念,镜像可以看做一个类class,它可以new很多对象,容器就相当于类的对象,每一次run都创建出一个新的对象

容器的操作

image.png

// 容器相关命令
docker container *

// 创建容器并启动(如果没有镜像则会拉取镜像再创建容器)
docker container run nginx

// 创建容器并启动,当停止容器时自动删除
docker container run --rm nginx

// eg: 创建mysql容器启动
// --name 设置容器名
// -e设置环境变量
// -d 后台运行
// -p 端口映射,宿主机端口:容器内端口
// mysql:tag 具体哪个镜像例如mysql:8.0.27, 不设置tag默认最新版本
docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -p 3306:3306 -d mysql:tag

// 启动容器
docker container start 容器ID/容器NAME

// 查看正在运行的容器
docker container ls

// 停止容器
docker container stop 容器NAME/容器ID(这个ID可以就写前面几位,只要能区分出来就好)
docker container stop 0b1

// 批量停止容器(写多个容器ID)
docker container stop 0b1 258 2b7

// 批量停止全部容器(两个命令相结合 docker container ls -aq 获取所有容器的ID)
docker container stop $(docker container ls -aq)

// 查看所有容器(已退出/正在运行)
docker container ls -a

// 删除容器
docker container rm 容器NAME/容器ID(这个ID可以就写前面几位,只要能区分出来就好)

// 批量删除全部容器(与停止同理)
docker container rm $(docker container ls -aq)
// 或者
docker system prune -f

// 强制删除容器(正在运行的容器不能删除,需要强制删除)
docker container rm -f 容器NAME/容器ID

其实也有docker container ps的命令,它和docker container ls用法几乎相同,现在最新版都是采用ls的命令了,当然以前的也可以用

上面我在执行run的时候可以发现,我们的命令行是执行中的(Mac OS/Linux)下,如果通过CTRL + C是会停止容器,因此如果需要在后台执行-d

-p为端口映射,宿主机访问8080会映射到容器中的80端口

// 后台执行
docker container run -d -p 8080:80 nginx

查看后台运行的nginx日志(不推荐)

docker attach 容器ID

另外一种查看日志的方式(动态查看日志-f)

docker container logs -f 容器ID/容器NAME

这里多说一句,不管是镜像ID还是容器ID不需要全部写上,只要能区分开容器ID之间的区别即可

容器的交互

之前使用nginx镜像,镜像中其实为我们已经设置了command,如果我们想在镜像中进行命令操作的话,以ubuntu举例,以下命令就可以进入到镜像中

// 创建容器并进入容器中
docker container run -it ubuntu 执行的命令
docker container run -it ubuntu sh

// 进入已经运行的容器中(退出也不会停止容器运行)
docker container exec -it 容器ID 执行的命令
docker exec -it 容器ID 执行的命令
docker exec -it 0b1 sh

// 查看容器的进程
docker container top 容器ID

镜像

镜像我们好比做一个提前声明好的类,用它来创建容器

获取镜像

  • Docker Hub 获取
  • Dockerfile构建
  • 文件导入

镜像的操作

// 查看镜像操作
docker image

// 第一种获取方式: Docker Hub 获取
// 拉取镜像(默认拉取latest)
docker image pull nginx

// 拉取指定版本镜像
docker image pull nginx:TAGS
docker image pull nginx:1.21.4

// 查看镜像列表
docker image ls

// 查看镜像详细信息
docker image inspect 镜像ID

// 删除镜像(如果容器正在使用的镜像则无法删除,停止容器了也不行)
docker image rm 镜像ID

// 删除全部镜像
docker image rm $(docker image ls -q)

// 强制删除全部镜像
docker image rm -f $(docker image ls -q)
// 或者
docker image prune -a

// 第二种获取方式: 文件导入
// 打包成镜像文件(-o为导出 )
docker image save nginx -o nginx.image

// 导入(-i为导入)
docker image load -i ./nginx.image

// 查看镜像构建历史
docker image history 镜像NAME/镜像ID

Dockerfile构建镜像

首先创建两个文件

// 第三种: Dockerfile
FROM ubuntu:latest
RUN  apt-get update && \
         DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y python3.9 python3-pip python3.9-dev
ADD test.py /
CMD ["python3","test.py"]
// test.py
print("hello docker")
// 构建 -t 命名 :版本,默认为latest .当前目录
docker image build -t sayhello .
docker image build -t sayhello:1.0.0 .

// 指定构建文件进行构建 -f 指定文件
docker image build -f ./Dockerfile-test -t sayhello:1.0.0 .

// 运行
docker container run sayhello

发布镜像

发布是有要求的 命名为: 用户ID/镜像名:版本

// 修改tag (id代表你的docker hub 的用户名)
docker image tag sayhello id/sayhello:1.0.0
docker image tag sayhello xiaoming/sayhello:1.0.0

// 登录
docker login

// 发布
docker image push id/sayhello:1.0.0

// 拉取(注意把本地的此镜像删除)
docker image pull id/sayhello:1.0.0

Dockerfile

通过Dockerfile构建镜像,首先要设置一个你当前镜像的一个基础镜像,就有点像前端我们打算写一个React组件,我们首先要:

import React form 'react';

同理,构建一个镜像,也和写一个组件差不多,在这里,我想多说两句,比如有的镜像如nginx,ubuntu,mysql等等,那nginx是怎么运行的呢?其实不管是什么镜像,底层都是linux,只不过这个linux很小,只保留了你这个程序所需要的一些功能,那么我们如何选择基础镜像,有三个原则

  • 有官方尽量选官方的
  • latest不一定最好,选择带有版本号的,这样比较稳定
  • 尽量选择体积小的镜像,如带有alpine tag的镜像

image.png

使用

RUN

每执行一次RUN就会为这个镜像生成一层,为了减少层数,尽量放在一个RUN指令中

RUN npm run build && \
    npm run uploadOss

COPY & ADD

将本地文件复制到容器中,如果未找到目录,则会创建这个目录

COPY index.html /app/index.html

两个命令的区别在于ADD可以在复制的同时还会解压缩文件

WORKDIR

相当于cd 到某个目录,如果这个目录不存在,会自动创建,还会影响后面的操作目录,会将index.html复制到/home

WORKDIR /home
COPY index.html index.html

EXPOSE

在当前容器中暴露一个端口给宿主机,它只是起到一个解释说明的作用,具体端口如何配置在后面会有介绍

ARG

通过ARG指令创建一个变量

注意定义变量时=不能有空格

ARG PYTHON_VERSION=3.9
RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y python${PYTHON_VERSION} python3-pip python${PYTHON_VERSION}-dev

也可以只定义,不赋值,后面通过命令的形式赋值

docker image build -t hello-python --build-arg PYTHON_VERSION=3.9

ENV

设置一个环境变量

ARG PYTHON_VERSION=3.9
RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y python${PYTHON_VERSION} python3-pip python${PYTHON_VERSION}-dev

设置的环境变量会保存至镜像内部,创建容器时可以使用

CMD

定义容器启动时执行什么命令,CMD只能设置一个,设置多个也只会执行最后一个命令,并且可以通过docker image container run进行覆盖

CMD ["npm", "run", "dev"]

ENTRYPOINT

ENTRYPOINT一定能够执行,不会被其他命令所覆盖,并且ENTRYPOINTENV可以一起使用

ENTRYPOINT ["echo"]
CMD []
docker container run -it test hello world

输出hello world

创建一个node镜像

这里以koa工程为例

mkdir koa-example && cd koa-example
npm init -y
npm i koa
touch server.js Dockerfile .dockerignore

下面我们通过Dockerfile构建镜像

// server.js
const Koa = require('koa');
const app = new Koa();

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);
// Dockerfile
FROM node:16.13.1-alpine3.14

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

// 复制当前目录文件到容器
COPY . .

EXPOSE 3000

CMD [ "node", "server.js" ]

这里可能会疑惑的是为什么package.json要单独COPY,因为如果放在一起的话,由于RUN是分层的,修改了这一层的代码我们就要重新的去构建一次,因此需要分开写

我们要把最有可能修改的那一层放在最后,docker在构建镜像时,是可以使用缓存的,但是如果我们再最上面一层被修改,即使下面没有发生变化,也将不会使用缓存构建镜像

// .dockerignore docker忽略COPY的文件
node_modules
npm-debug.log
docker image build -t koa-demo .
docker container run -d -p 3000:3000 koa-demo

上面的例子中也用到了.dockerfile,它与.gitignore是相似的,.gitignore不会被上传到orgin,而.dockerfile将文件中设置的文件忽略,不会被COPY

最后访问localhost:3000我们熟悉的Hello World是不是出来了呢👌

存储

在使用容器中,避免不了我们要保存一些数据,但是如果这个容器被删除,那么这个容器中的数据也会被一并删掉,但是很多情况下,比如数据库的一些数据或者日志,我们需要被保存下来,我们就需要一种方案将它们存储下来

有两种方案进行存储:

  • Data Volume
  • Bind Mount

VOLUME

这里用到了VOLUME,通过Dockerfile中设置VOLUME来保证数据的持久化

FROM node:16.13.1-alpine3.14

WORKDIR /app

COPY . .

VOLUME ["/app"]

CMD [ "node", "index.js" ]

在这个Dockerfile中设置了VOLUME,这个目录下的文件讲不会因容器的删除而删除了数据 我们可以通过命令查看所有的VOLUME

docker volume ls

image.png

查看详细信息

docker volume inspect 7786b04eb9bbb8f40266139be70f0f53477c2c0fb5c388632f67abadc903eb23

image.png

但是!这里会有一个问题,虽然这样把数据存储下来,但是如果我们重新的创建容器,我们又会生成一个新的volume,我们想每一次创建容器沿用之前的volume,因此我们需要指定一个volume命名

docker container run -d -v volume-name:/app volume-demo

这样设置命名后,即使再创建了容器,只要指定了volume名,就会使用这个volume

Bind Mount

可以映射一个宿主机的路径(windows使用${pwd}) 不需要设置VOLUME.

bind mount的作用是将宿主机路径的文件映射至容器中,容器可以进行文件一系列操作,但是容器所操作的文件其实就是宿主机被映射的文件,和VOLUME所不同的是,VOLUME指定以后会先生成出来一个VOLUME供你使用,但是它是被Docker内部所维护的

docker container run -it -v $(pwd):/app node

网络

宿主机与容器

对于docker的网络是如何传输,需要对网络协议有一定的了解.

首先,容器之间是可以通信的,容器和宿主机之间也是可以通信的,首先我们可以命令查看一下

docker network ls

image.png

我们可以看到有一个bridge,就是通过桥的方式进行连接的.也就是说docker为我们创建了一个局域网环境

docker network inspect 183

image.png

我们可以看到Gateway是172.17.0.1

下面我们创建一个nginx容器并进入其中的一个容器,访问一下宿主机

docker pull nginx:1.20.2-alpine
docker container run -d --name nginx1 nginx:1.20.2-alpine
docker container run -d --name nginx2 nginx:1.20.2-alpine
docker exec -it nginx1 sh

image.png

并且我们查询一下ip

image.png

// 宿主机查询容器ip
docker container inspect --format '{{.NetworkSettings.IPAddress}}' containerName

我们可以看到分配了一个局域网的ip地址.并且所有的容器都会分配一个局域网ip,它不仅仅可以访问宿主机,也可以访问其他的容器,另一个容器也就是172.17.0.3,可以启动另一个容器尝试一下

端口映射

上面我们谈到我们在docker的局域网内,那么如果宿主机的局域网下,其他设备想访问当前宿主机的docker容器那又怎么办呢

// 创建一个容器并进行端口映射
docker container run -d -p 80:80 --name nginx2 nginx:1.20.2-alpine

此时当我们访问127.0.0.1时访问的就是我们容器的nginx了

也就是说当访问我们的宿主机时192.168.31.27:80,此时会通过NAT转换为docker的局域网ip,再通过端口映射到nginx容器.

docker不仅仅有bridge模式,还有host模式

// 查看docker网络模式
docker network ls

image.png

host相当于与宿主机共享了网络,不需要再进行端口映射,通过host配置网络的容器,访问宿主机就是访问此容器

docker container run -d --name nginx4 --network host nginx:1.20.2-alpine

image.png

此时正在运行的容器POSTS没有端口转发了,某种意义来说,省去了端口转发,提升了性能

这里说一下,mac下设置host很有可能会失效,host模式可以在linux下使用

docker-compose

docker-compose是一个帮助开发者Docker使用的工具,它帮助我们进行以一些docker的命令操作,而不需要我们自己通过命令执行 比如如果我们想运行一个nginx容器

docker container run -d --name nginx1 nginx

而使用docker-compose

// docker-compose.yml
version: "3.8"

services:
  nginx-service:
    image: nginx
      ports:
        - 80:80
        

这样通过一个文件来执行命令,是不是方便多了

安装

  • Windows和Mac在安装Docker时docker-compose就自动安装了
  • Linux安装如下:
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version

使用

docker-compose语法的相关说明: 文档

由于docker-compose是使用的yaml的文件类型,如果不熟悉的朋友可以读一下阮一峰老师的yaml语言教程: yaml语法说明

下面是一个docker-compose的一个简易说明:

// docker-compose.yml
version: "3.8" docker-compose版本

services: # 容器,可以定义多个
  servicename: # 服务名字,对应命令参数--name,这个名字也是内部 bridge网络可以使用的 DNS name
    image: # 镜像名
    command: # 可选,如果设置,则会覆盖默认镜像里的 CMD命令
    environment: # 可选,相当于 docker run里的 --env
    volumes: # 可选,相当于docker run里的 -v
    networks: # 可选,相当于 docker run里的 --network
    ports: # 可选,相当于 docker run里的 -p
  servicename2:

volumes: # 可选,相当于 docker volume create

networks: # 可选,相当于 docker network create

对docker-compose进行命令操作要在docker-compose.yml的当前目录下,且文件名是docker-compose.yml

相关命令如下:

// 查看docker-compose命令
docker-compose
// 启动
docker-compose up
// 后台启动与container同理
docker-compose up -d
// 停止
docker-compose stop
// 查看列表
docker-compose ps

docker-compose创建的容器名是有规律的: 当前docker-compose所在的文件夹名 + serviceName + 数字,比如prd_ngnixServer_1,其实也可以修改,但是不推荐,会有一些问题,因此我们在使用docker-compose的时候尽量先把项目的文件夹命名提前起好

自定义

在配置镜像时,采用的是官方镜像,如果我们的一些配置在Dockerfile,那再拿过来又比较麻烦,因此,我们可以自定义镜像

services: # 容器,可以定义多个
  node-demo: 
    build: ./node # ./node/Dockerfile node是工程目录,目录中要有Dockerfile这个文件
    image: flask-demo:latest # 自定义镜像名,build要在image之前
  

举例

// mongo
// docker-compose.yml

version: "3"
services:
  mongo:
    image: mongo
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
    ports:
      - 27017:27017
    volumes:
      - /home/mongo:/data/db

// redis
// docker-compose.yml

version: "3"
services:
  redis-test:
    image: redis
    restart: always
    container_name: "redis-test"
    ports:
      - 6379:6379
    volumes:
      - /home/redistest:/data
    command: [ 'redis-server', "--requirepass", "111111" ]

拉取

可以通过提前拉取准备好镜像

docker-compose pull

Docker镜像加速

有的小伙伴在使用docker拉取镜像会特别的慢,一个镜像可能需要很久的时间,这里我给大家推荐两种方案

镜像加速器

这个适用于你购买了云服务器,但是这种办法最好,省心省事,你只需要按着官方配置就行,这里以阿里云举例

登录阿里云容器Hub

image.png 这里面列举了各种Linux版本的配置方法,直接命令复制粘贴,里面的加速器地址都给你生成好了,是不是很easy~

Docker-ce

访问相关链接按照文档操作,这种方案适用于我们自己本机配置使用

至此,Docker的基本知识也写的差不多了,这篇文章也是我边学边总结的一篇文章,如有遗漏或者错误,欢迎指正.