参考资料
菜鸟教程https://www.runoob.com/
B站视频https://www.bilibili.com/video/BV1Vs411E7AR?from=search&seid=2034793839753138066
微信公众号CloudMan 《每天5分钟玩转容器技术》
https://www.cnblogs.com/zhangxingeng/p/11236968.html

极客时间

基础前提知识:linux命令,Git知识

基础Docker命令

https://developer.aliyun.com/article/888720?spm=a2c6h.24874632.expert-profile.76.148447920Hnrj5

| 含义 | 命令 |

| | —- | —- | —- | |

| docker run |

| |

| docker ps |

| |

| docker pull |

| |

| docker push |

| |

| docker history 镜像名 |

| |

| docker exec |

| | 容器资源使用情况 | docker stats |

| | 查看容器内运行的进程 | docker top |

| |

| docker save 2d04e7c52fc3 > /home/nginx-save-v3.tar

|

| |

| docker build -t <路径>

|

|

docker run命令参数

可执行命令docker run —help查看

|

|

|

| | —- | —- | —- | | -i | interactive | Keep STDIN open even if not attached
打开STDIN,用于控制台交互 | | -t | tty | 分配tty设备,该可以支持终端登录,默认为false | | -d | detach | Run container in background and print container ID

指定容器运行于前台还是后台,默认为false | | -p | publish | Publish a container’s port(s) to the host
指定容器暴露的端口 | | -P(大写) |

| Publish all exposed ports to random ports | | -v | volume | 给容器挂载存储卷,挂载到容器的某个目录 | | -e |

| 指定环境变量,容器中可以使用该环境变量 | | —link |

| 指定容器间的关联,使用其他容器的IP、env等信息 | | —volumes-from |

| 给容器挂载其他容器上的卷,挂载到容器的某个目录 | | —name |

|

| | —privileged |

|

| | —restart | 默认为no | 指定容器停止后的重启策略:
no:容器退出时不重启
on-failure:容器故障退出(返回值非零)时重启
always:容器退出时总是重启 |

Docker简介

一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。

Docker的应用场景
Web 应用的自动化打包和发布;
自动化测试和持续集成、发布。
在服务型环境中部署和调整数据库或其他的后台应用。
从头编译或者扩展现有的 OpenShift 或 Cloud Foundry 平台来搭建自己的 PaaS 环境。

什么是容器

容器是一种轻量级、可移植、自包含的软件打包技术,使应用程序可以在几乎任何地方以相同的方式运行。开发人员在自己笔记本上创建并测试好的容器无需任何修改就能够在生产系统的虚拟机、物理服务器或公有云主机上运行。

为什么需要容器
为什么需要容器?容器到底解决的是什么问题?
简要的答案是:容器使软件具备了超强的可移植能力






容器的核心技术

一个正在运行的 Docker 容器,其实就是一个启用了多个 Linux Namespace 的应用进程,而这个进程能够使用的资源量,则受 Cgroups 配置的限制。

容器的核心技术是 Cgroup + Namespace。

  • Cgroup: 资源限制
  • namespace: 访问隔离

namespace

  1. $ docker run -it busybox /bin/sh
  2. / #
  3. / # ps
  4. PID USER TIME COMMAND
  5. 1 root 0:00 /bin/sh
  6. 10 root 0:00 ps

可以看到,我们在 Docker 里最开始执行的 /bin/sh,就是这个容器内部的第 1 号进程(PID=1),而这个容器里一共只有两个进程在运行。这就意味着,前面执行的 /bin/sh,以及我们刚刚执行的 ps,已经被 Docker 隔离在了一个跟宿主机完全不同的世界当中。

本来,每当我们在宿主机上运行了一个 /bin/sh 程序,操作系统都会给它分配一个进程编号,比如 PID=100。

而现在,我们要通过 Docker 把这个 /bin/sh 程序运行在一个容器当中。这种机制,其实就是对被隔离应用的进程空间做了手脚,使得这些进程只能看到重新计算过的进程编号,比如 PID=1。可实际上,他们在宿主机的操作系统里,还是原来的第 100 号进程。这种技术,就是 Linux 里面的 Namespace 机制

Namespace 技术实际上修改了应用进程看待整个计算机“视图”,即它的“视线”被操作系统做了限制,只能“看到”某些指定的内容。但对于宿主机来说,这些被“隔离”了的进程跟其他进程并没有太大区别。

image.png

不应该把 Docker Engine 或者任何容器管理工具放在跟 Hypervisor 相同的位置,因为它们并不像 Hypervisor 那样对应用进程的隔离环境负责,也不会创建任何实体的“容器”,真正对隔离环境负责的是宿主机操作系统本身。
在这个对比图里,我们应该把 Docker 画在跟应用同级别并且靠边的位置。这意味着,用户运行在容器里的应用进程,跟宿主机上的其他进程一样,都由宿主机操作系统统一管理,只不过这些被隔离的进程拥有额外设置过的 Namespace 参数。而 Docker 项目在这里扮演的角色,更多的是旁路式的辅助和管理工作。

有利就有弊,基于 Linux Namespace 的隔离机制相比于虚拟化技术也有很多不足之处,其中最主要的问题就是:隔离得不彻底。
首先,既然容器只是运行在宿主机上的一种特殊的进程,那么多个容器之间使用的就还是同一个宿主机的操作系统内核。
其次,在 Linux 内核中,有很多资源和对象是不能被 Namespace 化的,最典型的例子就是:时间。
此外,由于上述问题,尤其是共享宿主机内核的事实,容器给应用暴露出来的攻击面是相当大的,应用“越狱”的难度自然也比虚拟机低得多。

Cgroups

Linux Cgroups 就是 Linux 内核中用来为进程设置资源限制的一个重要功能。Linux Cgroups 的全称是 Linux Control Group。它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。





容器与 虚拟机

使用虚拟化技术作为应用沙盒,就必须要由 Hypervisor 来负责创建虚拟机,这个虚拟机是真实存在的,并且它里面必须运行一个完整的 Guest OS 才能执行用户的应用进程。这就不可避免地带来了额外的资源消耗和占用。
而相比之下,容器化后的用户应用,却依然还是一个宿主机上的普通进程,这就意味着这些因为虚拟化而带来的性能损耗都是不存在的;而另一方面,使用 Namespace 作为隔离手段的容器并不需要单独的 Guest OS,这就使得容器额外的资源占用几乎可以忽略不计

容器管理

基本组成:镜像,容器,仓库镜像 就相当于是一个 root 文件系统。
比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。
容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

仓库可看成一个代码控制中心,用来保存镜像。Docker 容器通过 Docker 镜像来创建。
一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag), 代表这个仓库源的不同个版本;每个标签对应一个镜像。如 ubuntu 仓库源里,有 15.10、14.04 等多个不同的版本,
我们使用 REPOSITORY:TAG 来定义不同的镜像。

Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建 镜像所需的指令和说明。

docker 可将容器对外提供服务的端口映射到 host 的某个端口,外网通过该端口访问容器。容器启动时通过-p参数映射端口:-p 80

除了映射动态端口,也可在 -p 中指定映射到 host 某个特定端口,例如可将 80 端口映射到 host 的 8080 端口:-p 8080:80

启动容器:docker run -it —name ubuntu-test ubuntu /bin/bash

docker run -dp 8083:80 —name docker-test8083 docker/getting-started
参数说明:

按用途容器大致可分为两类:服务类容器和工具类的容器
1. 服务类容器以 daemon 的形式运行,对外提供服务。比如 web server,数据库等。
通过 -d 以后台方式启动这类容器是非常合适的。如果要排查问题,可以通过 exec -it 进入容器。

  1. 工具类容器通常给能我们提供一个临时的工作环境,通常以 run -it 方式运行


    查看所有的容器:docker ps -a
    停止容器 docker stop 容器ID
    启动一个已经停止的容器:docker start b750bbbcfd88 (容器ID)
    进入容器:docker exec -it 243c32535da7 /bin/bash

镜像管理

容器镜像rootfs:它只是一个操作系统的所有文件和目录,并不包含内核,最多也就几百兆。
而相比之下,传统虚拟机的镜像大多是一个磁盘的“快照”,磁盘有多大,镜像就至少有多大。

在 rootfs 的基础上,Docker 公司创新性地提出了使用多个增量 rootfs 联合挂载一个完整 rootfs 的方案,这就是容器镜像中“层”的概念。

image.png

  1. $ docker image inspect ubuntu:latest
  2. ...
  3. "RootFS": {
  4. "Type": "layers",
  5. "Layers": [
  6. "sha256:f49017d4d5ce9c0f544c...",
  7. "sha256:8f2b771487e9d6354080...",
  8. "sha256:ccd4d61916aaa2159429...",
  9. "sha256:c01d74f99de40e097c73...",
  10. "sha256:268a067217b5fe78e000..."
  11. ]
  12. }

可以看到,这个 Ubuntu 镜像,实际上由五个层组成。这五个层就是五个增量 rootfs,每一层都是 Ubuntu 操作系统文件与目录的一部分;而在使用镜像时,Docker 会把这些增量联合挂载在一个统一的挂载点上。

【小结】通过“分层镜像”的设计,以 Docker 镜像为核心,来自不同公司、不同团队的技术人员被紧密地联系在了一起。而且,由于容器镜像的操作是增量式的,这样每次镜像拉取、推送的内容,比原本多个完整的操作系统的大小要小得多;而共享层的存在,可以使得所有这些容器镜像需要的总空间,也比每个镜像的总和要小。这样就使得基于容器镜像的团队协作,要比基于动则几个 GB 的虚拟机磁盘镜像的协作要敏捷得多。


推送镜像(push)到制品库:

前期工作:

  1. vi /etc/docker/daemon.json
  2. systemctl daemon-reload
  3. systemctl restart docker
  4. docker login

https://blog.csdn.net/weixin_30348519/article/details/95178047

编写dockerfile,执行docker build构建好镜像 或从tar包load:docker load -i xxx.tar;

查看镜像docker images -a(为了查看镜像名)

打标签:docker tag 镜像名images-name 制品路径/镜像名:版本号
(注意:Artifactory的路径,不可以添加artifactoy字样,
虽然制品库中页面路径会显示artifactory,否则推送不上去)

推送:docker push 制品路径/镜像名:版本号

Dockerfile详解

FROM:指定基础镜像,必须为第一个命令
MAINTAINER: 维护者信息
RUN:构建镜像时执行的命令

ADD:将本地文件添加到容器中,tar类型文件会自动解压(网络压缩资源不会被解压),可以访问网络资源,类似wget
COPY:功能类似ADD,但是不会自动解压文件,也不能访问网络资源

CMD:构建容器后调用,也就是在容器启动时才进行调用。
CMD不同于RUN,CMD用于指定在容器启动时所要执行的命令,而RUN用于指定镜像构建时所要执行的命令。
CMD指令也有两种格式:
shell 格式:CMD <命令>
exec 格式:CMD [“可执行文件”, “参数1”, “参数2”…]

比如:
CMD cat /etc/redhat-release查看系统类型版本。
如果换算成exec格式,上面那命令就等于CMD [ “sh”, “-c”, “cat /etc/redhat-release” ] 。也就是说,CMD后面如果是跟的shell命令,那么实际底层执行是用exc的sh -c的方式
exec命令格式里的第一小段才是主进程,上面的那两个例子命令,主进程就是 sh ,而不是cat /etc/redhat-release

cat /etc/redhat-release 和 systemctl start mysqld这两个shell命令 有个特点,执行完后就会返回结果退出。因此,sh 到时候也会退出。sh主进程退出了,那么容器的灵魂就没有了,那么容器也不再会运行了。换句话说,灵魂也就持续存在了一两秒…
想要让容器一直运行,那么CMD就得写对。最好是用exec的命令格式。比如,启动运行nginx、mysql等,应该是类似这样写:
CMD [“nginx”, “-g”, “daemon off;”]
CMD [“/usr/bin/mysqld_safe”]

ENTRYPOINT:配置容器,使其可执行化

ENTRYPOINT与CMD非常类似,不同的是通过docker run执行的命令不会覆盖ENTRYPOINT,
而docker run命令中指定的任何参数,都会被当做参数再次传递给ENTRYPOINT。

让容器表现得像一个可执行程序?这个要怎么理解呢? 我们看下下面的例子
FROM centos:7.2
ENTRYPOINT[“/bin/cat”]
这个镜像做成后运行将带有cat的功能。我们在运行这个镜像的时候跟上一个文件路径,那么就会返回输出这个文件内容

ENTRYPOINT一般跟CMD配合起来一起使用。因为CMD里的内容能作为参数传到ENTRYPOINT里使用。
FROM centos:7.2
ENTRYPOINT[“vmstat”,”3”]
CMD[“5’]
ENTRYPOINT里原本是执行vmstat每空3秒不停循环输出vmstat监控信息,然后有了CMD参数后,传入了一个5,那么vmstat结果就只能输出5次了

|

|

|

| | —- | —- | —- | | RUN |

|

镜像操作 | | COPY |

| | | ADDVOLUM |

| | | ONBUILD |

| | | ENTRYPOINY |

|

容器启动 | | CMD |

| | | EXPOSE |

|

配置 | | ARG |

| | | WORKDIR |

| | | USER |

| | |

|

|

| |

|

|

| |

|

|

|

Dockerfile中只允许有一个ENTRYPOINT命令,多指定时会覆盖前面的设置,而只执行最后的ENTRYPOINT指令。
另外:参考https://cloud.tencent.com/developer/article/1116794

ENV:设置环境变量
EXPOSE: 声明容器要使用的端口。
注意,这里用的是声明这个词而不是定义。因此,在容器启动后并不是就立即使用EXPOSE声明的端口,这只是在dockerfile里跟大家说明下,这个镜像做好后打算使用什么端口。
EXPOSE并不会让容器的端口访问到主机。
要使其可访问,需要在docker run运行容器时通过-p来发布这些端口,或通过-P参数来发布EXPOSE导出的所有端口

VOLUME:用于指定持久化目录,指定数据的存储挂载点。有的容器涉及到一些数据的持久化,比如mysql这样的容器,它就需要定义一个数据卷路径存储数据文件。

WORKDIR:工作目录,类似于cd命令
通过WORKDIR设置工作目录后,
Dockerfile中其后的命令RUN、CMD、ENTRYPOINT、ADD、COPY等命令都会在该目录下执行。
在使用docker run运行容器时,可以通过-w参数覆盖构建时所设置的工作目录

USER: 指定运行容器时的用户名或 UID
ARG:用于指定传递给构建运行时的变量.
与 ENV 作用一至。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。
构建命令 docker build 中可以用 —build-arg <参数名>=<值> 来覆盖

ONBUILD:用于设置镜像触发器。
以当前镜像为基础,在下一个镜像构建的时候去运行一些命令。简单的说就是为下一个镜像做准备,相当于下个镜像制作的“触发器”
注: 个触发器只在“子辈”的镜像里有效果,在“孙辈”的镜像里没效果,隔一代,继承效果就消失了。

类别 指令
来源、环境设置 FROM、ENV、LABEL
维护者信息 MAINTAINER
镜像操作 RUN、COPY、ADD、VOLUME、ONBUILD
配置 EXPOSE、ARG、WORKDIR、USER
容器启动 CMD、ENTRYPOINT

构建Java镜像的10个最佳实践
https://www.kubernetes.org.cn/8989.html

docker存储设置

给容器挂载数据卷很简单,有三种方式。第一种是用docker volume 命令创建挂载;
第二种就是通过docker run 命令 -v 的参数指定数据卷挂载路径;
docker run -v宿主机里绝对路径:容器里绝对路径 -it 镜像名 /bin/bash

第三种就是在做镜像的时候在dockerfile里用VOLUME指令设置好数据卷路径。
https://cloud.tencent.com/developer/article/1116800

容器网络
https://cloud.tencent.com/developer/article/1359581

容器API
https://cloud.tencent.com/developer/article/1371802

批量导出导入镜像
https://github.com/yuchen16/imageTools
https://note.youdao.com/ynoteshare1/index.html?id=2c82333cae70cd9e08b6b0efa9d76e52&type=note

问题解决:
Linux Docker中无法使用 systemd(systemctl) 相关命令的原因是1号进程不是 init,而是其他例如 /bin/bash ,所以导致缺少相关文件无法运行。(System has not been booted with systemd as init system (PID 1). Can’t operat)
解决方案:/sbin/init
例如:Ubuntu 18.04 ,
docker run -tid —name test —privileged=true ubuntu:18.04 /sbin/init
docker exec -it test /bin/bash

PS:—privilaged=true一定要加上的。

WSL下宿主机文件复制到容器内部:
docker cp mnt/源文件路径 容器ID:容器目录

问题解决:
3月12日下午,docker突然一直处于starting状态。

查看日志:

[16:52:48.447][LoggingMessageHandler][Info ] [34271ffe] GET http://localhost/version
[16:52:48.450][LoggingMessageHandler][Info ] [34271ffe] GET http://localhost/version -> 200 OK (took 2ms)
[16:52:48.481][LoggingMessageHandler][Info ] [efbe6130] GET http://localhost/hyperv/vhdx-size?path=C:%5CProgramData%5CDockerDesktop%5Cvm-data%5CDockerDesktop.vhdx
[16:52:48.483][LoggingMessageHandler][Info ] [efbe6130] GET http://localhost/hyperv/vhdx-size?path=C:%5CProgramData%5CDockerDesktop%5Cvm-data%5CDockerDesktop.vhdx -> 200 OK (took 2ms)
[16:52:48.493][LoggingMessageHandler][Info ] [64fd0b7b] POST http://localhost/migrate/app
[16:52:48.494][LoggingMessageHandler][Info ] [64fd0b7b] POST http://localhost/migrate/app -> 204 NoContent (took 0ms)
[16:52:48.537][LoggingMessageHandler][Info ] [4c1f9a31] POST http://localhost/versionpack/enable
[16:52:48.541][LoggingMessageHandler][Info ] [4c1f9a31] POST http://localhost/versionpack/enable -> 204 NoContent (took 3ms)
[16:52:48.549][LoggingMessageHandler][Info ] [2d14f7d6] POST http://localhost/cloudcli/toggle
[16:52:48.550][LoggingMessageHandler][Info ] [2d14f7d6] POST http://localhost/cloudcli/toggle -> 204 NoContent (took 1ms)
[16:52:48.579][LoggingMessageHandler][Info ] [1f5be665] POST http://localhost/dns/refresh-hosts
[16:52:48.783][LoggingMessageHandler][Info ] [1f5be665] POST http://localhost/dns/refresh-hosts -> 204 NoContent (took 205ms)

解决办法:
执行wsl —unregister docker-desktop,然后重启docker
参考https://github.com/docker/for-win/issues/6971