一、容器

容器解决的问题

开发人员通常使用多种服务(比如MQ、Cache、DB)构建和组装应用,而且可能会部署到不同环境。

  • 应用包含多种服务,这些服务有自己所依赖的库和软件包
  • 存在多部署环境,服务在运行时可能需要动态迁移到不同环境中,开发环境->测试环境->生产环境。

容器的优势

对于开发人员:Build Once、Run Anywhere
容器意味着环境隔离和可重复性。开发人员只需为应用创建一次运行环境,然后打包成容器便可在其他机器上运行。另外,容器环境与所在的 Host 环境是隔离的,就像虚拟机一样,但更快更简单。

对于运维人员:Configure Once、Run Anything
只需要配置好标准的runtime环境,服务器就可以运行任何容器。这使得运维人员的工作变得更高效、一致和可重复。容器消除了开发、测试、生产环境的不一致性。

二、Docker概念

docker架构

Docker 采用的是 Client/Server架构。客户端向服务器发送请求,服务器负责构建、运行和分发容器。客户端和服务器可以运行在同一个 Host 上,客户端也可以通过socket或RESTAPI与远程的服务器通信。

img

  • Docker_Host:
    • 安装Docker的主机
  • Docker Daemon:
    • 运行在Docker主机上的Docker后台进程,负责创建、运行、监控容器,构建存储镜像
  • Client:
    • 操作Docker主机的客户端(命令行、UI等)
  • Registry:
    • 镜像仓库
    • Docker Hub
  • Images:
    • 镜像,带环境打包好的程序,可以直接启动运行。只读模板,通过它可以创建Docker容器
  • Containers:
    • 容器就是docker镜像的运行实例,用户可以通过CLI或是API启动、停止、移动或删除容器。对于应用软件,镜像是软件生命周期的构建和打包阶段,而容器是启动和运行阶段。

docker镜像

每一个镜像都是从基础镜像开始的

base镜像

base镜像有两层含义:

  • 不依赖其他镜像,从 scratch构建;
  • 其他镜像可以以之为基础进行扩展。

所以,能称作 base镜像的通常都是各种 Linux发行版的 Docker镜像,比如Ubuntu、Debian、CentOS等。

为什么一个centos的镜像只有200MB?

Linux内核由内核空间和用户空间构成:

  1. rootfs

    内核空间是kernel,Linux 刚启动时会加载bootfs文件系统,之后 bootfs会被卸载掉。

    用户空间的文件系统是 rootfs,包含我们熟悉的ldev、/proc、/bin等目录。
    对于 base镜像来说,底层直接用Host 的 kernel,自己只需要提供rootfs就行了。
    而对于一个精简的OS,rootfs可以很小,只需要包括最基本的命令、工具和程序库就可以了。

  2. base镜像提供的是最小安装的linux发行版

    1
    2
    3
    FROM scratch
    ADD centos-7-docker.tar.xz /
    CMD ["/bin/basg"]
  3. 支持多种Linux OS

    ubuntu使用upstart管理服务,apt管理软件包;而CentOS7使用systemd和yum这些都是用户空间上的区别,Linux kernel差别不大。

    image-20211008225305039

容器只能使用Host的kernel,并且不能修改。
所有容器都共用host 的 kernel,在容器中没办法对kernel升级。如果容器对kernel 版本有要求(比如应用只能在某个 kernel版本下运行),则不建议用容器,这种场景虚拟机可能更合适。

镜像的特点

分层结构

新镜像是从base镜像一层一层叠加生成的。

好处就是共享资源,有多个镜像都是从相同的base镜像构建而来,那么Docker Host只需要在磁盘上保存一份base镜像,就可以为所有容器服务了,而且镜像的每一层都可以共享。

写时复制

当容器启动时,一个新的可写层被加载到镜像的顶部。这一层叫容器层,所有对容器的改动,无论是添加、删除、函数修改文件都只发送在容器层中。容器层下面所有的镜像层是可写的。

任何对于文件的操作都会记录在容器层,例如:修改文件时容器层会把在镜像层找到的文件拷贝到容器层然后进行修改,删除文件时则会在容器层内记录删除文件的条目。

读写层png

所以,镜像可以被多个容器共享。

联合挂载

容器层会从上到下去下一层的镜像层中去获取文件

当容器启动时,一个新的可写层writable被加载到镜像的顶部,这一层通常被称为容器层container,容器层之下的都叫做镜像层。

典型的Linux在启动后,会将rootfs置为只读readonly并进行一些列检查,然后将其切换为可读写readwrite供用户使用。在Docker中起初也是将rootfs以只读的readonly的方式加载并检查,然后接下来利用union mount联合挂载将一个readwrite文件系统挂载在readonlyrootfs之上,并且允许再次将下层的文件系统设定为只读readonly,并向上叠加。这样一组只读readonly和一个可写writeable的结果就构成了一个容器container的运行目录,每个镜像被称为一层layer

三、安装

centos安装

1
https://docs.docker.com/engine/install/centos/

1.移除旧的docker

1
2
3
4
5
6
7
8
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine

2.配置yum源

1
2
3
4
sudo yum install -y yum-utils
sudo yum-config-manager \
--add-repo \
http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

3.安装docker

1
2
3
4
sudo yum install -y docker-ce docker-ce-cli containerd.io

#以下是在安装k8s的时候使用
yum install -y docker-ce-20.10.7 docker-ce-cli-20.10.7 containerd.io-1.4.6

4.启动

1
2
#开机自启并启动
systemctl enable docker --now

5、配置加速

这里额外添加了docker的生产环境核心配置cgroup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://82m9ar63.mirror.aliyuncs.com"],
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

四、docker基本命令

image-20211008204653350

1.寻找镜像

docker hub寻找镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
docker pull nginx  #下载最新版

#镜像名:版本名(标签)
docker pull nginx:1.20.1

docker pull redis #下载最新
docker pull redis:6.2.4

## 下载来的镜像都在本地
docker images #查看所有镜像

redis = redis:latest
#删除镜像
docker rmi 镜像名:版本号/镜像id

2.启动容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

#【docker run 设置项 镜像名 】 镜像启动运行的命令(镜像里面默认有的,一般不会写)

# -d:后台运行
# --restart=always: 开机自启
docker run --name=mynginx -d --restart=always -p 88:80 nginx


# 查看正在运行的容器
docker ps
# 查看所有
docker ps -a
# 删除停止的容器
docker rm 容器id/名字
docker rm -f mynginx #强制删除正在运行中的


#停止容器
docker stop 容器id/名字
#再次启动
docker start 容器id/名字

#应用开机自启
docker update 容器id/名字 --restart=always

3、修改容器内容

修改默认的index.html 页面

1.进入容器修改

1
2
# 进入容器内部的系统,修改容器内容
docker exec -it 容器id /bin/bash

2、挂载数据到外部修改

1
2
3
4
5
6
docker run --name=mynginx   \
-d --restart=always \
-p 88:80 -v /data/html:/usr/share/nginx/html:ro \
nginx

# 修改页面只需要去 主机的 /data/html

4、提交修改

1
2
3
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

docker commit -a "liuqi" -m "首页变化" 341d81f7504f mynginx:v1.0

传输镜像

1
2
3
4
5
# 将镜像保存成压缩包
docker save -o abc.tar mynginx:v1.0

# 别的机器加载这个镜像
docker load -i abc.tar

5、推送远程仓库

1
2
docker tag local-image:tagname new-repo:tagname
docker push new-repo:tagname
1
2
3
4
5
6
7
8
9
10
11
12
# 把旧镜像的名字,改成仓库要求的新版名字
docker tag mynginx:v1.0 liuqi/mynginx:v1.0

# 登录到docker hub
docker login
docker logout(推送完成镜像后退出)

# 推送
docker push liuqi/mynginx:v1.0

# 别的机器下载
docker pull liuqi/mynginx:v1.0

6、补充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
docker logs 容器名/id   排错

#进入容器
docker exec -it 容器id /bin/bash

# docker 经常修改nginx配置文件
docker run -d -p 80:80 \
#ro容器里面不可以修改 rw容器里面也可以修改
-v /data/html:/usr/share/nginx/html:ro \
-v /data/conf/nginx.conf:/etc/nginx/nginx.conf \
--name mynginx-02 \
nginx

#把容器指定位置的东西复制出来
docker cp 5eff66eec7e1:/etc/nginx/nginx.conf /data/conf/nginx.conf
#把外面的内容复制到容器里面
docker cp /data/conf/nginx.conf 5eff66eec7e1:/etc/nginx/nginx.conf

五、打包镜像Dockerfile

镜像特点

镜像的缓存特性

Docker会缓存已有镜像的镜像层,构建新镜像时,如果某镜像层已经存在,就直接使用,无须重新创建。

如果出现Using cache,就说明命中缓存。

如果我们希望在构建镜像时不使用缓存,可以在 docker build命令中加上--no-cache参数。Dockerfile 中每一个指令都会创建一个镜像层,上层是依赖于下层的。无论什么时候,只要某一层发生变化,其上面所有层的缓存都会失效。
也就是说,如果我们改变Dockerfile指令的执行顺序,或者修改或添加指令,都会使缓存失效。

调试Dockerfile

总结一下通过Dockerfile构建镜像的过程:

  • 从base镜像运行一个容器。
  • 执行一条指令,对容器做修改。
  • 执行类似 docker commit 的操作,生成一个新的镜像层。
  • Docker再基于刚刚提交的镜像运行一个新容器。
  • 重复2~4步,直到Dockerfile中的所有指令执行完毕。

从这个过程可以看出,如果 Dockerfile由于某种原因执行到某个指令失败了,我们也将能够得到前一个指令成功执行构建出的镜像,这对调试 Dockerfile非常有帮助。我们可以运行最新的这个镜像定位指令失败的原因。

通过docker run -it 镜像id 启动镜像的一个容器,定位问题。

常用的Dockerfile命令

  1. FROM:指定基础镜像。

  2. MAINTAINER:作者

  3. COPY

    将文件从build context复制到镜像。

    COPY支持两种形式:

    1
    2
    COPY src dest 
    COPY ["src", "dest"]。

    注意:src只能指定build context中的文件或目录。

  4. ADD:与COPY类似,从 build context 复制文件到镜像。不同的是,如果 src是归档文件(tar、zip、tgz、xz 等),文件会被自动解压到dest。

  5. ENV:设置环境变量,环境变量可被后面的指令使用。例如:

    1
    ENV MY VERSION 1.3 RUN apt-get install -y mypackage=$MY VERSION
  6. EXPOSE
    指定容器中的进程会监听某个端口,Docker 可以将该端口暴露出来。我们会在容器网络部分详细讨论。

  7. VOLUME
    将文件或目录声明为volume。

  8. WORKDIR
    为后面的RUN、CMD、ENTRYPOINT、ADD或COPY指令设置镜像中的当前工作目录。

  9. RUN
    在容器中运行指定的命令。

  10. CMD
    容器启动时运行指定的命令。

    Dockerfile 中可以有多个CMD指令,但只有最后一个生效。CMD可以被docker run 之后的参数替换。

  11. ENTRYPOINT
    设置容器启动时运行的命令
    Dockerfile 中可以有多个ENTRYPOINT 指令,但只有最后一个生效。CMD或docker run之后的参数会被当作参数传递给ENTRYPOINT。

RUN vs CMD vs ENTRYPOINT

  • RUN: 执行命令并创建新的镜像层,RUN经常用于安装软件包
  • CMD:设置容器启动后默认执行的命令和参数,但CMD会被docker run后面跟着的命令行参数替换。
  • ENTRYPOINT:配置容器启动时运行的命令。

1.Shell和Exec

shell格式

1
2
3
4
5
6
instruction command

#例如
RUN apt-get install python3
CMD echo "Hello world"
ENTRYPOINT echo "Hello world"

命令执行时,shell底层会调用/bin/sh -c [command]

1
ENV name Cloud Man ENTRYPOINT echo "Hello, $name"

Exec格式

1
2
3
4
5
6
instruction ["executable","param1","param2"]

#例如
RUN ["apt-get","isntall","python"]
CMD ["/bin/echo","Hello world"]
ENTRYPOINT ["/bin/echo","Hello world"]

命令执行时,会直接调用[command],不会被shell解析。

1
ENV name Cloud Man ENTRYPOINT["/bin/sh", "-c","echo Hello, $name"]

2.RUN

通常用于安装用于和软件包。RUN命令在当前镜像的顶部执行命令,并创建新的镜像层。

3.CMD

CMD指令允许用户指定容器的默认执行的命令。此命令会在容器启动且 docker run没有指定其他命令时运行。
如果docker run指定了其他命令,CMD指定的默认命令将被忽略。如果 Dockerfile 中有多个CMD指令,只有最后一个CMD有效。CMD有三种格式:

  • Exec格式:CMD[" executable" ,"param1 ","param2"]这是 CMD的推荐格式。
  • CMD["param1 ","param2"]为 ENTRYPOINT提供额外的参数,此时ENTRYPONT必须使用 Exec格式。
  • Shell格式:CMD command param1 param2

第二种格式CMD [“param1”,”param2”]要与 Exec格式的 ENTRYPOINT 指令配合使用,其用途是为 ENTRYPOINT 设置默认的参数。

4.ENTRYPOINT

ENTRYPOINT指令可让容器以应用程序或者服务的形式运行

ENTRYPOINT看上去与CMD很像,它们都可以指定要执行的命令及其参数。不同的地方在于ENTRYPOINT不会被忽略,一定会被执行,即使运行 docker run时指定了其他命令。
ENTRYPOINT有两种格式:

  • Exec格式:ENTRYPOINT ["executable", ""param1", "param2""]这是ENTRYPOINT 的推荐格式。
  • Shell格式:ENTRYPOINT command paraml param2

ENTRYPOINT两种格式的效果差别很大。

Exec 格式

ENTRYPOINT的 Exec格式用于设置要执行的命令及其参数,同时可通过CMD 提供额外的参数。

ENTRYPOINT 中的参数始终会被使用,而CMD的额外参数可以在容器启动时动态替换掉

1
2
3
4
5
6
7
ENTRYPOINT ["/bin/echo", "Hello"]CMD [ "world"]

#docker run -it[image]
#Hello world

#docker run -it [image] CloudMan
#Hello CloudMan

Shell模式会忽略如何CMD或docker run 提供的参数。

打包一个web应用

1.编写Dockerfile

1
2
3
4
5
6
FROM openjdk:8-jdk-slim
LABEL maintainer=leifengyang

COPY target/*.jar /app.jar

ENTRYPOINT ["java","-jar","/app.jar"]

2.打包

1
docker build -t java-demo:v1.0 .

六、服务的启动

redis

1
2
3
4
5
6
docker run -v /data/redis/redis.conf:/etc/redis/redis.conf \
-v /data/redis/data:/data \
-d --name myredis \
-p 6379:6379 \
redis:latest redis-server /etc/redis/redis.conf \
--appendonly yes