文章目录

  • 一、Docker 和虚拟机有啥区别?
  • 二、Docker 组成
  • 三、Docker 安装(centos)
  • 四、配置阿里云镜像加速器
  • 五、docker 如何工作
  • 六、Docker基本命令
  • 6.1 镜像操作
  • 6.2 容器命令
  • docker run [可选参数] ImageName
  • docker ps 【显示最近创建的容器】
  • docker ps -a `--no-trunc` 【显示所有容器,并且展示完整的启动命令】
  • 退出容器
  • docker rm ContainerId 删除容器
  • 启动停止
  • **后台启动容器**
  • docker logs 看日志
  • docker top ContainerID 容器内进程信息
  • `docker stat` 看容器的状态
  • docker inspect ContainerID 看容器元数据
  • 进入当前容器
  • 从容器拷贝文件到宿主机
  • 镜像打标签
  • 查看容器内部网络配置
  • `--network` (重要)
  • 七、docker命令小结
  • 八、docker安装练习
  • 8.1 安装nginx
  • 8.2 安装tomcat
  • 8.3 安装ES + kibana
  • 九、portainer
  • 十、Docker Image原理
  • 10.1 联合文件系统 UFS
  • 10.2 bootfs & rootfs
  • 10.3 分层下载理解
  • 十一、镜像的读写
  • 十二、docker commit 生成镜像
  • 参考


一、Docker 和虚拟机有啥区别?

简单说,你部署10个虚拟机,会有10个操作系统;部署10个容器,只需要1个操作系统。容器就是比较特殊的进程。

为啥Docker比虚拟机快?

  • Docker比虚拟机有着更少的抽象层
  • Docker利用的是的宿主机的内核,虚拟机需要的是GuestOS。新建一个容器的时候,docker不需要像虚拟机一样重新加载一个OS 内核,避免 引导。虚拟机利用宿主机的内核,省略了这个过程,可达到秒级启动。

    简单比较一下:

项目

docker

Vm

虚拟化类型

OS虚拟化

硬件虚拟化

性能

= 物理机性能

5%~20% 损耗

隔离性

NS 隔离


QoS

Cgroup弱


安全性



可迁移性



二、Docker 组成

poetry install 指定镜像 portainer安装自己的镜像_运维

  • 镜像 -->就是个模板。Tomcat镜像–> docker run --> Tomcat 容器
  • 容器–> 资源隔离:独立运行一个或一组应用
  • 仓库 --> 存放镜像的地方。 分 公有、私有的仓库

三、Docker 安装(centos)

1、查旧版本

sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

2、设定仓库

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

3、更新yum 索引

yum makecache fast

4、安装(默认是最新的)

sudo yum install docker-ce docker-ce-cli containerd.io

5、启动docker

sudo systemctl start docker

6、测试

docker version
sudo docker run hello-world

会发现:

Unable to find image 'hello-world:latest' locally。

这是因为本地没有hello-world的镜像,docker会自己从远程仓库拉取镜像。拉取成功后,会出现 Hello from docker的提示。

那为了启动hello-world服务,docker 做了什么呢?

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

7、查看镜像

docker images

8、卸载docker

sudo yum remove docker-ce docker-ce-cli containerd.io
sudo rm -rf /var/lib/docker

注意一下:/var/lib/docker 也是docker的默认工作路径

四、配置阿里云镜像加速器

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://uxk0ognt.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker 【重启docker daemon进程】

五、docker 如何工作

client-server 结构,Docker的守护进程运行在主机上,客户端通过Socket 访问服务端。

poetry install 指定镜像 portainer安装自己的镜像_docker_02

六、Docker基本命令

6.1 镜像操作

  • docker search KW
  • --filter docker search kafka --filter=Stars=1000 【根据 docker hub中对镜像的评分筛选】
    不过,最好的查找镜像的方式还是去 docker hub网站上搜索,上面有更详尽的内容。
  • docker pull ImageName docker pull mysql:latest 这里 latest 是一个 tag (其实就是版本号),默认情况下docker会拉取最新的镜像。
    拉取的时候,会有下列的输出。这种下载方式是Linux中分层下载
  • [root@localhost ~]# docker pull mysql:latest
    latest: Pulling from library/mysql  
    a076a628af6f: Already exists    【这里是Linux中的分层下载,联合文件系统】
    f6c208f3f991: Pull complete 
    ...
    944ba37e1c06: Pull complete 
    Digest: sha256:feada149cb8ff54eade1336da7c1d080c4a1c7ed82b5e320efb5beebed85ae8c
    Status: Downloaded newer image for mysql:latest
    docker.io/library/mysql:latest	 【这里是真实的下载地址】
  • docker images 查看
    docker images -q -a
  • -a all images
  • -q quiet ,only show ImageIDs
  • docker rmi ImageName
  • docker rmi mysql 删除 MySQL的镜像,默认也是删除 【latest】的镜像
  • -f 强删 ,如果不加-f,没法删除正在运行的容器的镜像
  • docker rmi -f $(docker images -q -a) 删除所有的镜像
  • docker inspect ImageName 查看一个镜像的元数据信息
  • ImageID
  • ContainerConfig

6.2 容器命令

下面都以安装一个 centos 容器为栗子:

docker run [可选参数] ImageName
* --name  指定容器名字,比如一个集群中有N个Nginx,每个名字不一样
* -it  以命令行交互的方式启动 
* -d  后台启动  
* -p  指定端口  【可以和宿主机形成端口映射】
* -P 随机指定端口
* --rm  容器停止后即删除容器,多用于测试

比如: docker run -it centos /bin/bash 启动 centos,并进入 /bin/bash 进程。

[root@localhost ~]# docker run -it centos /bin/bash
[root@d500c59b6e86 /]# ls
bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@d500c59b6e86 /]# exit  【从容器退出】
exit
docker ps 【显示最近创建的容器】
* -a : all  
* -q  : only show containerID
docker ps -a --no-trunc 【显示所有容器,并且展示完整的启动命令】
退出容器
*	exit   : 容器停止并退出
*	Ctrl P  Q   : 容器不停止退出
docker rm ContainerId 删除容器
* docker rm -f ContainerID 强制删除
* docker rm $(docker ps -qa) 删除所有的容器
* docker ps -qa |xargs docker rm  效果同上
启动停止
* docker start ContainerID
* docker restart ContainerID
* docker stop ContainerID
* docker kill ContainerID
后台启动容器
docker run -d centos 这样以后台方式启动后,然后docker ps -a 未能发现到正常运行的容器。容器自动退出了!此处有坑:
**docker容器使用后台运行,就必须有一个前台进程,docker发现没有 应用,就会自动停止**!!!
```	 
[root@localhost ~]# docker run -d centos
03cbee2b8365b4c259948cbe8cb55d3a39f912a1961727cec184342c8397da11
[root@localhost ~]# docker ps -a
CONTAINER ID   IMAGE        COMMAND                  CREATED          STATUS                        PORTS                    NAMES
03cbee2b8365   centos       "/bin/bash"              6 seconds ago    Exited (0) 5 seconds ago                               ha
```
docker logs 看日志
* -f   follow  【和 `tail -f `里的`-f`含义是一样的】
* -t   显示时间戳
* -n    显示末尾几行
```
[root@localhost ~]# docker run -it centos /bin/bash
[root@55b522d5d7ae /]# [root@localhost ~]# docker logs -ftn 10 55b522d5d7ae
(这里啥也没有,因为确实没打印日志)
```
为了能够打印出日志方便查,启动容器进入 /bin/bash,启动一个shell 脚本:
```
[root@localhost ~]# docker run -it centos /bin/bash -c "while true;do echo test;sleep 5;done"
test
test
[root@localhost ~]# docker logs -ftn 10 b59e8aebc937
2021-02-19T02:15:05.247176048Z test
2021-02-19T02:15:10.262505079Z test
```
docker top ContainerID 容器内进程信息

下面是 查看到的进程信息: Pi

[root@localhost ~]# docker top b59e8aebc937
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                2936                2915                0                   10:14               pts/0               00:00:00            /bin/bash -c while true;do echo test;sleep 5;done
root                3078                2936                0                   10:19               pts/0               00:00:00            /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 5
docker stat 看容器的状态

能动态看docker daemon上所有docker容器的状态,比如:

CONTAINER           CPU %               MEM USAGE / LIMIT       MEM %               NET I/O             BLOCK I/O           PIDS
912ed29b4ab7        5.83%               8.633 GiB / 251.3 GiB   3.43%               0 B / 0 B           0 B / 8.19 kB       316
docker inspect ContainerID 看容器元数据

容器的元数据是个JSON:

  • mount信息
  • 容器网络配置
  • 容器状态
  • 容器ID、创建时间、名字
进入当前容器

第一种方式: docker exec

  • -it 以交互命令行的方式进入

比如我们需要进入容器修改一些配置,

[root@localhost ~]# docker exec -it b59e8aebc937 /bin/bash
[root@b59e8aebc937 /]# ls
bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

第二种方式: docker attach ContainerID

[root@localhost ~]# docker attach b59e8aebc937
test
test

两种方式有啥区别?
docker exec : 进入容器后开启一个新的终端,可在内操作
docker attach:进入容器正在执行的终端,不会启动新的进程。

从容器拷贝文件到宿主机

docker cp ContainerID:/fromFilePath toFilePath

比如:
docker cp b59e8aebc937:/home/a.txt /home

引申问题:
手动拷贝是一个手动动作,使用 -v (卷) 能实现实时同步。

镜像打标签

docker tag

查看容器内部网络配置

docker exec -it ContainerID ip addr 这个命令和 docker exec -it ContainerID /bin/bash 有点类似,exec -it 的存在配合最后的ip addr 奏效,ip addr 可以是其他命令

--network (重要)
docker run -id -p ${port}:${port} --name=${dockername} \
--restart=always --net=host  \
-v /etc/localtime:/etc/localtime:ro -v /data/my-project/logs:/app/logs \
-e JAVA_OPTS="-Dspring.profiles.active=${env} --spring.cloud.nacos.discovery.ip=${publish_server}" \
-e TZ=Asia/Shanghai 
${imagename}

这个命令里,有两个坑货,其一是对时区的的处理,和宿主机/容器的时间同步; 其二是网络--net的类型

七、docker命令小结

poetry install 指定镜像 portainer安装自己的镜像_poetry install 指定镜像_03

八、docker安装练习

8.1 安装nginx

只需要一句命令搞定:
[root@localhost ~]# docker run --name nginx0 -d -p 8080:80 nginx:1.18.0
然后本地用浏览器访问出现Nginx 欢迎界面表示安装成功。

1、如果本地没有 1.18.0的镜像,docker会自动pull

2、8080:80 是将docker的80端口映射到 宿主机的 8080,也就是说假如我们访问宿主机的8080 ,实际上请求会被 nginx 的docker 容器从 80 端口接收。

poetry install 指定镜像 portainer安装自己的镜像_容器_04

8.2 安装tomcat

从docker hub上找到安装文档。极简安装也就是一句话:
docker run -it --rm -p 8888:8080 tomcat:9.0 注意: 这里的 --rm 是指这个容器停了就删除,多用于测试。
执行命令后,会出现如下的tomcat 启动日志:

[root@localhost ~]# docker run -it --rm -p 8888:8080 tomcat:9.0
Using CATALINA_BASE:   /usr/local/tomcat
Using CATALINA_HOME:   /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME:        /usr/local/openjdk-11
Using CLASSPATH:       /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
Using CATALINA_OPTS:   
NOTE: Picked up JDK_JAVA_OPTIONS:  --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
19-Feb-2021 03:21:12.760 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version name:   Apache Tomcat/9.0.41

从浏览器 访问 ,会出现tomcat 的 404 NotFound页面。
如果ctrl C 退出 console,容器不仅会退出,而且会删除。

现在我们换个姿势 启动:
docker run -it -d --name tomcat0 -p 8888:8080 tomcat:9.0 扎样会手动拉起 docker 容器;进入容器看 tomcat 的目录结构:

[root@localhost ~]# docker exec -it 37a7e7a1e3c1 /bin/bash
root@37a7e7a1e3c1:/usr/local/tomcat# ls -l
total 128
-rw-r--r--. 1 root root 18982 Dec  3 11:48 BUILDING.txt
....
drwxr-xr-x. 2 root root     6 Jan 13 08:25 webapps
drwxr-xr-x. 7 root root    81 Dec  3 11:45 webapps.dist
drwxrwxrwx. 2 root root     6 Dec  3 11:43 work

发现:
1、容器中没有 ll vi 等shell工具
2、 webapps 目录为空,因此浏览器访问没有任何内容

这是因为:
这个镜像为了能够尽量精简,将许多不核心的命令都阉割掉了。
做个测试:
将 webapps.dist 中的内容完整拷贝到 webapps 目录下,然后浏览器访问
http://192.168.**.***:8888/index.jsp ,则可以显示 tomcat 的启动页面。

引申问题:
如果部署容器,还要进入容器岂不是十分麻烦,可以在容器外提供一个映射路径到 webapps。只需要在容器外部署,自动同步到内部岂不妙哉。

8.3 安装ES + kibana

ES 有几个特点:

  • 特别吃内存
  • 配置的端口比较多
  • ES的数据一般需要挂载到安全目录
  • --net somenetwork

我们 以 development 模式启动,从ES的docker hub 看到,这也不过一句命令的事儿:
docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:6.8.13

测试一下是不是真的启动成功:
[root@localhost ~]# curl localhost:9200

启动后发现, ES实在是太占资源 了。
使用 docker stats fa09dc0f7672 查看容器状态:

CONTAINER ID   NAME            CPU %     MEM USAGE / LIMIT     MEM %     NET I/O     BLOCK I/O       PIDS
fa09dc0f7672   elasticsearch   5.54%     1.175GiB / 1.777GiB   66.15%    656B / 0B   695MB / 633kB   44

那如何增加内存限制呢? 【使用 -e 参数修改环境配置】
首先关闭正在运行的 ES 容器,然后添加 JVM参数,再启动
docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms64m -Xmx512m" elasticsearch:6.8.13

重新看下资源:

docker  stats ContainerID

CONTAINER ID   NAME            CPU %     MEM USAGE / LIMIT     MEM %     NET I/O     BLOCK I/O       PIDS
a5a7c21f3224   elasticsearch   1.37%     416.8MiB / 1.777GiB   22.91%    656B / 0B   249MB / 646kB   45

引申问题:
如果我们在同一个宿主机内启动一个Kibana容器 ,连接到这个 ES容器,由于容器之间互相隔离,那两者如何连通?

  • 直连?
  • localhost ?

九、portainer

portainer: Docker的GUI 工具。
在CI/CD 的场景下,Rancher 是更广泛使用的。

从docker hub 找到这个工具的使用方式:

docker volume create portainer_data
docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce

安装OK后,浏览器打开:

http://192.168.*.*:9000 注意这里 不是 8000 的端口,然后得到这样的界面:

poetry install 指定镜像 portainer安装自己的镜像_容器_05

这个工具在大厂用得不是特别多,但是在中小公司倒是十分实用。

十、Docker Image原理

DockerImage:本质是个软件包,打包了运行环境和应用软件,准确点说,是把应用运行的所有代码、运行时、环境变量、配置文件统一打包起来了。
怎么获取镜像:

  • 自己制作
  • 别人拷贝
  • 从远程仓库

10.1 联合文件系统 UFS

1、什么是UFS
一种分层、轻量级、高性能的文件系统,支持对文件系统的修改作为一次次的提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。 UFS是docker Image 的实现基础。镜像可以通过分层来继承, 基于基础Image,可以制作各种具体应用镜像

特性:一次加载多个文件系统,但是从外面看起来,只能看到一个文件系统,UFS会把各层文件系统叠加,这样最终的文件系统会包含所有底层的文件和目录。

2、如何理解UFS

我们拿GIt来理解下:

  • Git的版本控制每次commit就会有一次的记录,后面的commit对前面的commit可以有改动。
  • 比如,我们安装一个Tomcat的Docker容器: tomcat 的镜像先commit 了 OS内核(e.g. centos),然后commit JDK,最后 commit tomcat 。最终得到的image,看起来这是一tomcat image,实际分了好几层。
  • 假如我们又要安装一个 Jetty 的Docker 容器,Jetty Image依赖的 操作系统 和 JDK 就不需要再去 pull 了。

举个栗子:假如现在要制作一个自己的镜像,dockerfile:

From debian
RUN apt-get install emacs
RUN apt-get install apache2
CMD['/bin/bash']

a) 新镜像并不是从Scratch开始,而是直接在 debian 基础上构建

b) 安装 emacs

c) 安装 apache2

d) 容器启动时运行bash

poetry install 指定镜像 portainer安装自己的镜像_poetry install 指定镜像_06

可以看到:

新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层

3、使用UFS的好处
UFS的最大的一个好处就是 - 共享资源

比如:有多个镜像都从相同的 base 镜像构建而来,那么宿主机只需在磁盘上保存一份base镜像,同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。

(看这个图,UFS 像不像一个洋葱)

poetry install 指定镜像 portainer安装自己的镜像_docker_07

10.2 bootfs & rootfs

  • bootfs(boot file system) 主要包含 bootloader和kernel , bootloader主要是引导加载kernel。Linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。
  • rootfs (root file system),在bootfs之上 (图中的 Debian)。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等。【所以有人把 容器称作 小的 虚拟机环境

Q: 为啥平时我们装的虚拟机都是 几个G,而docker 容器才几百M?

A :对于一个精简版本的OS , rootfs 很小,只需要包含基本的命令、库、工具即可,因为底层直接使用host和kernel,用户只要提供 rootfs 即可。对于不同的Linux发行版本,bootfs 基本一致,rootfs 会有所差别,所以不同的发行版可公用 bootfs

10.3 分层下载理解

以pull 一个 redis 为例子:

[root@localhost ~]# docker pull redis
Using default tag: latest
latest: Pulling from library/redis
a076a628af6f: Already exists 	  【这一层已经下载过了,不用继续下了】
f40dd07fe7be: Pull complete 
ce21c8a3dbee: Pull complete 
ee99c35818f8: Pull complete 
56b9a72e68ff: Pull complete 
3f703e7f380f: Pull complete 
Digest: sha256:0f97c1c9daf5b69b93390ccbe8d3e2971617ec4801fd0882c72bf7cad3a13494
Status: Downloaded newer image for redis:latest
docker.io/library/redis:latest

顺便诊断下 镜像看看:
docker inspect redis

截取其中的rootfs (刚好也是 6 个layers)

"RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:xxx",
                "sha256:xxx",
                "sha256:xxx",
                "sha256:xxx",
                "sha256:xxx",
                "sha256:xxx"
            ]
        },

有一点需要重点了解下:
在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合。
比如1 : 下面这个镜像包含了两个镜像层的6个文件:(注意这 6 个文件是没有冲突的。)

poetry install 指定镜像 portainer安装自己的镜像_docker_08


比如2: 这个复杂一点的三层镜像,在外部看来也是有 6个文件,最上层的文件7 由于是文件5 的更新版本,已经产生了替换。Docker 通过存储引擎(新版本使用快照机制)实现镜像层堆栈,并保证多镜像层对外展示为统一的文件系统。 Linux上可用的存储引擎有Btrfs 、Zfs 等,每种对应到Linux中对应的文件系统或块存储技术。

poetry install 指定镜像 portainer安装自己的镜像_Docker_09


在镜像打包的时候,所有镜像层堆叠合并,对外提供统一的视图:

poetry install 指定镜像 portainer安装自己的镜像_运维_10

十一、镜像的读写

Docker镜像都是只读的,容器启动时,一个新的可写层被加载到了镜像的顶部,这就是所谓的容器层,容器层之下的都叫做镜像层

解释一下:
我们基于一个Tomcat 镜像,启动一个Tomcat容器,对容器的Tomcat改了点配置,这个修改只是对 容器层,不会改变原先的 镜像。
如果我们希望修改能对所有后来启动的容器都有效果,那么可以基于原镜像再添加一层(再打包并 commit 一次)

十二、docker commit 生成镜像

docker commitgit commit 有异曲同工之妙,和VM的 快照功能有点类似。

docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

  • -m 提交描述信息
  • -a 作者信息
  • REPOSITORY: 目标镜像名
  • TAG : 版本

下面举个栗子:
前面提到,官方的tomcat 镜像是精简过的,webapps 目录下没有内容。直接拉起容器后,通过浏览器访问得到 404 。现在我们搞一个自己的镜像,webapps中有从webapps.dist 中复制过去的内容。然后基于自己制作的镜像,启动新的容器。

[root@localhost ~]# docker run -it -d -p 8888:8080 tomcat:9.0     
[root@localhost ~]# docker exec -it /bin/bash 06825c314e50
root@06825c314e50:/usr/local/tomcat# cp webapps.dist/* webapps/ -r  
curl localhost:8080  【测试容器是否启动正常】

然后commit 一个自己的镜像:
[root@localhost ~]# docker commit -m="add webapps" -a="author" 06825c314e50 mytomcat:02

看一下镜像是不是打成功了:
[root@localhost ~]# docker images
REPOSITORY               TAG       IMAGE ID       CREATED         SIZE
mytomcat                 02        2f6f33afc42c   6 seconds ago   654MB
redis                    latest    621ceef7494a   5 weeks ago     104MB

从新镜像拉起 容器 
[root@localhost ~]# docker run --name mytomcat -d -p 8081:8080 mytomcat:02
e10bc76ab632c721edd0f80e501476757af3e7985dd9f918a8125507a8ec6cf8

浏览器访问或者curl一下 (8081 端口),看是否成功

参考

  • http://www.dockerone.com/article/783