入坑docker之基础(一)

docker基础理论

1.1 docker与虚拟机的区别

在这里插入图片描述从结构上来看,容器和虚拟机还是有很大不同的。
vm与docker框架,直观上来讲虚拟机的Guest层,还有Hypervisor层在Docker上已经被Docker Engine层所取代,
其中Guest OS 是虚拟机安装的操作系统,是一个完整的系统内核,另外Hypervisor可以理解为硬件虚拟化平台,它在后Host OS以内核驱动的形式存在。也就是说虚拟机实现资源的隔离的方式是利用独立的Guest OS,以及利用Hypervisor虚拟化CPU、内存、IO等设备来实现的
然而Docker并没有和虚拟机一样利用一个独立的Guest OS执行环境的隔离,它是使用Linux namespace来隔离其运行环境,使得容器中的进程看起来就像在一个独立的环境中运行。如,A Container 之中PID=1是A程序,而B Container之中的PID=1同样可以是A程序。
在这里插入图片描述
但是光有运行环境隔离还不够,因为这些进程还是可以不受限制地使用系统资源,比如网络、磁盘、CPU以及内存等。所以一方面为了防止它占用了太多的资源而影响到其它进程;另一方面,在系统资源耗尽的时候,Linux内核会触发OOM (out of memory killer,OOM会在系统内存耗尽的情况下跳出来,选择性的干掉一些进程以求释放一些内存)这会让一些被杀掉的进程成了无辜的替死鬼,因此为了让容器中的进程更加可控,Docker使用Linux cgroups来限制容器中的进程允许使用的系统资源 Linux Cgroup可以让系统中所运行任务(进程)的用户定义组分配资源—比如CPU时间、系统内存、网络带宽。简单来说docker是依赖 Linux 内核特性 Namespace 和Cgroup 技术来实现的,本质来说:你运行在容器的应用在宿主机来说还是一个普通的进程,还是直接由宿主机来调度的,即docker直接使用硬件资源,所以相对来说,性能的损耗就很少,这也是 Docker 技术的重要优势。
在这里插入图片描述
默认情况下,一个容器没有资源限制,几乎可以使用宿主主机的所有资源。

1.2 docker基本原理

Docker 是一个Client -Server (C/S)结构的系统,Docker的守护进程(Docker daemon)运行在主机上,通过Socket从客户端访问
Docker-Server 接收到 Docker-Client 的指令,就会执行这个命令
Client 通过接口与Server进程通信实现容器的构建,运行和发布。
client和server可以运行在同一台集群,也可以通过跨主机实现远程通信。
在这里插入图片描述
Docker 客户端是 Docker 用户与 Docker 交互的主要方式。当您使用 docker 命令行运行命令时, Docker 客户端将这些命令发送给服务器端,服务端将执行这些命令。 docker 命令使用 docker API 。 Docker 客户端可以与多个服务端进行通信。

1.3 核心概念

1.3.1 镜像(image)

镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需要的所有内容,包括代码,运行时库、环境变量和配置文件。
docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统称之为UnionFS,最底层为Bootfs,其上为rootfs
在这里插入图片描述

bootfs(boot file system)主要包含bootloader和kernel。 bootloader主要是引导加载kernel, Linux刚启动时会加载bootfs文件系统,当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs,以节约内存资源。
rootfs (root file system) 位于bootfs之上,表现为docker容器的根文件系统(典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件),传统模式中,系统启动之时,内核挂载rootfs时会将其挂载为只读模式,完整性自检完成后,将其挂载为读写模式。docker中rootfs由内核挂载只读模式,而后通过联合挂载技术额外挂载一个可写层(写时复制)。
在这里插入图片描述
其中位于上层的是父镜像层,最下面一层是基础镜像,最上层的是“可读写层(容器层)” ,其余层都是“只读层(镜像层)”,删除容器后可写层一定会被删除(所以一般会把数据挂载宿主机上),所以容器的改动,都只会发生在容器层,因为其他层都只是可读的。即Docker 镜像都是只读的,当容器启动时,一个新的可写层加载到镜像的顶部!这一层就是我们通常说的容器层,容器之下的都叫镜像层!
一些细节:

  • 添加文件:新文件会添加在容器层中
  • 读取文件:从上往下找,在镜像层找到后,会复制到容器层
  • 修改文件:与读文件类似,在下层找到后复制到容器层才能修改
  • 删除文件:同上
#### 命令
docker pull redis
### 测试

在这里插入图片描述
可以观察到,image是一层层的下载,而且有些已经重复的文件,不会再额外下载。
思考:为什么Docker要分层下载呢?
上图【和上边的图一样的】:
在这里插入图片描述
所有的 Docker镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上,创建新的镜像层。
举一个简单的例子,假如基于 Ubuntu Linux16.04创建一个新的镜像,这就是新镜像的第一层;如果在该镜像中添加 Python包,就会在基础镜像层之上创建第二个镜像层;如果继续添加一个安全补丁,就会创建第三个镜像层。
在这里插入图片描述

在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合。
如下图,每个镜像层包含3个文件,而整体的大镜像包含了来自两个镜像层的6个文件。
在这里插入图片描述

也就是说在pull的时候,docker内部会先到各个镜像层中扫描是否已存在该文件,如果存在则不在下载。
文件有更新:
如下图,在内部有7个文件,但在外部看来整个镜像只有6个文件,这是因为最上层中的文件7是文件5的一个更新版
在这里插入图片描述
这种情況下,上层镜像层中的文件覆盖了底层镜像层中的文件。这样就使得文件的更新版本作为一个新镜像层添加到镜像当中。Docker通过存储引擎(新版本采用快照机制)的方式来实现镜像层堆栈,并保证多镜像层对外展示为统一的文件系统。所有镜像层堆叠并合并,对外提供统一的视图。
在这里插入图片描述
总结:

  • docker 镜像代表了容器的文件系统里的内容,是容器的基础,镜像一般是通过 Dockerfile 生成的
  • docker的镜像是分层的,所有的镜像(除了基础镜像)都是在之前镜像的基础上加上自己这层的内容生成的
  • 每一层镜像的元数据都是存在 json文件中的,除了静态的文件系统之外,还会包含动态的数据

1.3.2 Container (容器)

容器由最上面一个可写的容器层,以及若干只读的镜像层组成,容器的数据就存放在这些层中。这样的分层结构最大的特性是 Copy-on-Write:
1、新数据会直接存放在最上面的容器层。
2、修改现有数据会先从镜像层将数据复制到容器层,修改后的数据直接保存在容器层中,镜像层保持不变。
3、如果多个层中有命名相同的文件,用户只能看到最上面那层中的文件。
镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例 一样,镜像是静态的定义。镜像和容器唯一区别在于容器的最上面那一层是可读可写的。
可以说容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等 ,其实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。
容器存储层的数据存储方式和之前的镜像层一样,都是用使用的是分层存储。容器存储层的生命周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。所以容器不应该向其存储层内写入任何数据
,容器存储层要保持无状态化。所有的文件写入操作,都应该使用数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此, 使用数据卷后,容器可以随意删除、重新 run ,数据却不会丢失。

1.3.2.1 容器的隔离机制

Docker的隔离性主要运用Namespace 技术,namespace(命名空间)可以隔离哪些

  1. Mount Namespace - 用于隔离文件系统的挂载点
  2. UTS Namespace - 用于隔离 HostName 和DomianName
  3. IPC Namespace - 用于隔离进程间通信
  4. PID Namespace - 用于隔离进程 ID
  5. Network Namespace - 用于隔离网络
  6. User Namespace - 用于隔离用户和用户组UID/GID
    有了以上的隔离,我们认为一个容器可以与宿主机和其他容器是隔离开的。恰巧Linux 的namespace可以做到这些。

那么是如何隔离的呢???
以PID namespaces为例:
首先PID namespaces用来隔离进程的ID空间。上源码

package main

import (
    "os/exec"
    "syscall"
    "os"
    "log"
)

func main() {
    cmd := exec.Command("sh")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID,
    }
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    if err := cmd.Run(); err != nil {
        log.Fatal(err)
    }
}

它对进程PID重新标号,即两个不同namespace下的进程可以有同一个PID。所以PID namespace可以嵌套,也就是说有父子关系,目前PID namespace最多可以嵌套32层,由内核中的宏MAX_PID_NS_LEVEL来定义。
Linux下的每个进程都有一个对应的/proc/PID目录,该目录包含了大量的有关当前进程的信息。也就是说内核为所有的PID namespace维护了一个树状结构,最顶层的是系统初始时创建的,被称为root namespace,它创建新的PID namespace被称为child namespace(树的子节点),而原先的PID namespace就是新创建的PID namespace的parent namespace(树的父节点)。通过这种方式,不同的PID namespace会形成一个层级体系。所属的父节点可以看到子节点中的进程,并可以通过信号等方式对子节点中的进程产生影响。反过来,子节点却不能看到父节点PID namespace中的任何内容。
在这里插入图片描述

  • PID namespace中的init进程
    在传统的Unix系统中,PID为1的进程时init,地位非常特殊。它作为所有进程的父进程,维护一张进程表,不断检查进程状态,一旦某个子进程因为父进程错误成为了“孤儿”进程,init就会负责收养这个子进程并最终回收资源,结束进程。所以在要实现的容器中,启动的第一个进程也需要实现类似init的功能,维护所有后续启动进程的状态。
    当系统中存在的树状嵌套结构的PID namespace时,若某个子进程成为了孤儿进程,收养孩子进程的责任就交给了孩子进程所属的PID namespace中的init进程。
    PID namespace维护这样一个树状结构,有利于系统的资源的控制与回收。因此,如果确实需要在一个Docker容器中运行多个进程,最先启动的命令进程应该是具有资源监控与回收等管理能力的,如bash。
  • 信号与init进程
    内核还为PID namespace中的init进程赋予了其他特权—信号屏蔽。如果init中没有编写处理某个信号的代码逻辑,那么与init在同一个PID namespace下的进程(即使有超级权限)发送非他的信号都会屏蔽。这个功能主要作用就是防止init进程被误杀。
    那么,父节点PID namespace中的进程发送同样的信号给子节点中的init的进程,这会被忽略吗?父节点中的进程发送的信号,如果不是SIGKILL(销毁进程)或SIGSTOPO(暂停进程)也会诶忽略。但如果发送SIGKILL或SIGSTOP,子节点的init会强制执行(无法通过代码捕捉进行特殊处理),也就是说父节点中的进程有权终止子进程。
    一旦init进程被销毁,同一PID namespace中的其他进程也所致接收到SIGKIKLL信号而被销毁。理论上,该PID namespace也不复存在了。但如果/proc/[pid]/ns/pid处于被挂载或打开的状态,namespace就会被保留下来。然而,被保留下来的namespace无法通过setns()或者fork()创建进程,所以实际上并没有什么作用。
    当一个容器内存在多个进程时,容器内的init进程可以对信号进行捕获,当SIGTERM或SIGINT等信号到来时,对其子进程做信息保存、资源回收等处理工作。在Docker daemon的源码中也可以看到类似的处理方式,当结束信号来临时,结束容器进程并回收相应资源。
    总结:
    每个PID namespace都有自己的计数程序。内核为所有的PID namespace维护了一个树状结构,最顶层的是系统初始时创建的,我们称之为root namespace。
    不同的PID namespaces会形成一个等级体系。所属的父节点可以看到子节点中的进程,并可以通过信号等方式对子节点中的进程产生影响。反过来,子节点不能看到父节点PID namespace中的任何内容。
    在root namespace中可以看到所有的进程,并且递归包含所有子节点中的进程。

1.3.3 Repository (仓库)

Docker Registry 是存放镜像仓库的的地方,包括镜像的层次结构和元数据,在启动时docke daemon 会试图从本地获取,本地找不到就会从Registry(可配置)中下载该镜像
在这里插入图片描述

Docker 仓库的概念跟 Git 类似,注册服务器(Registry)可以理解为 GitHub 这样的托管服务。实际上,一个 Docker Registry 中可以包含多个仓库 (Repository) ,每个仓库可以包含多个标签 (Tag),每个标签对应着一个镜像。通俗的说就是:注册服务器上可以有很多仓库(类似我们常用的项目代码库),每个仓库可以有很多分支(版本),每个分支都是一个都是一个镜像。所以说,镜像仓库是 Docker 用来集中存放镜像文件的地方类似于我们之前常用的代码仓库。仓库分为公开仓库(Public)和私有仓库(Private)两种形式。最大的公开仓库是 Docker Hub,存放了数量庞大的镜像供用户下载。国内的公开仓库包括 时速云 、网易云 等。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。
总结:
一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过<仓库名>:<标签>的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签.。
在这里插入图片描述

1.4 Run的流程分析图

### 命令
docker run hello-world

###测试:
yangxing@Albertz  ~  docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Already exists
Digest: sha256:5122f6204b6a3596e048758cabba3c46b1c937a46b5be6225b835d091b90e46c
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

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.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

 yangxing@Albertz  ~ 

测试中可以看出,首先Client 发起一个 【docker run】命令,然后Docker_host(本机)的Docker daemon会去查看是否在本地的Images 已经存在,如果存在则运行【Containers】docker容器,如果不存在,则先去【Registry】远程库【docker pull】拉取到本地,然后再运行【Containers】docker容器。若在远程仓库中找不到该容器会报错。具体如下图所示:
在这里插入图片描述

二、 docker基础命令

命令参考文档:https://docs.docker.com/engine/reference/commandline/

2.1 通用命令

2.1.1 查看docker版本信息

docker --version

2.1.2 查看帮助

docker 命令  --help

2.1.3 查看docker系统信息

 docker info

2.1.4 当前目录

[root@c8ef56c4a132 /]# docker run -it -v ${PWD}/testmp/imagesv:/home centos bash
### 在linux和mac中可以用变量
${PWD}
###  表示当前目录

2.2 镜像命令

2.2.1 查看镜像

docker images    ## 查看所有镜像


## 测试
yangxing@Albertz  ~  docker images
REPOSITORY   TAG       IMAGE ID       CREATED      SIZE
nginx        latest    f0b8a9a54136   3 days ago   133MB

### 使用帮助
yangxing@Albertz  ~  docker images --help

Usage:  docker images [OPTIONS] [REPOSITORY[:TAG]]

List images

Options:
  -a, --all          ### 查看所有镜像
  -q,--quiet        ###  只看docker image id那一列

2.2.2 搜索镜像

docker search 镜像名

### 测试
NAME                              DESCRIPTION   STARS     OFFICIAL   AUTOMATED
mysql                MySQL is a widely used…    10877     [OK]
mariadb              MariaDB Server is a hi…    4103      [OK]
mysql/mysql-server   Optimized MySQL Server…    808                   [OK]

## 可选项,通过收藏过滤
--filter=STARS=5000        # 搜索收藏超过5000的镜像

2.2.3 拉取镜像,从中央仓库中下载镜像到本地

docker pull 镜像名  #  默认最新版本

## 测试
yangxing@Albertz  ~  docker pull mysql
Using default tag: latest     ### 默认最新版
latest: Pulling from library/mysql
69692152171a: Already exists     ## 已存在不再下载
1651b0be3df3: Pull complete      ### 分层存储机制
951da7386bc8: Pull complete
0f86c95aa242: Pull complete
37ba2d8bd4fe: Pull complete
6d278bb05e94: Pull complete
497efbd93a3e: Pull complete
f7fddf10c2c2: Pull complete
16415d159dfb: Pull complete
0e530ffc6b73: Pull complete
b0a4a1a77178: Pull complete
cd90f92aa9ef: Pull complete
Digest: sha256:d50098d7fcb25b1fcb24e2d3247cae3fc55815d64fec640dc395840f8fa80969   ## 防伪标志-验签信息
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest    #####  镜像真实地址 
###  所以上述命令等价于: docker pull docker.io/library/mysql:latest

### 指定版本
docker pull 镜像:tag

2.2.4 删除镜像

docker rmi imageId   # 删除某一个镜像
## 也可以通过版本进行删除
docker rmi imagename:tag

# 删除所有镜像
docker rmi -f $(docker images -aq)   # 参数 -f:强制性删除
## 如删除正在运行中的images
docker rmi -f imagesid     ### imageid相同可以用 imagename:tag 形式

2.2.5 保存镜像

当需要把一台机器上的镜像迁移到另一台机器的时候,需要保存镜像与加载镜像。
docker save imagesname > save.tar

## 测试
 yangxing@Albertz  ~  docker save hello-world > ./hello.tar
 yangxing@Albertz  ~  ll
total 3
-rw-r--r--   1 yangxing  staff    24K  5 16 15:58 hello.tar   ### 存到了本地
drwxr-xr-x@ 13 yangxing  staff   416B  6 14  2020 learn source
drwxr-xr-x   2 yangxing  staff    64B 12 22  2019 opt

### 可选参数
-o, --output string   Write to a file, instead of STDOUT
写入到文件中,而不是标准输出流
2.2.6 加载镜像
docker load < save.tar

### 测试
 yangxing@Albertz  ~  docker load < ./hello.tar
Loaded image: hello-world:latest
 yangxing@Albertz  ~  docker images
REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
hello-world   latest    d1165f221234   2 months ago   13.3kB

2.3 容器命令

2.3.1 运行容器

docker run [option] image

### 参数说明
Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
   -a, --attach=[]            登录容器(以docker run -d启动的容器)
   -t, --tty=false            分配tty设备,该可以支持终端登录
   -u, --user=""              指定容器的用户
   -v, --volume=[]            给容器挂载存储卷,挂载到容器的某个目录
   -P, --publish-all=false    指定容器暴露的端口,待详述
  -p, --publish=[]           指定容器暴露的端口,待详述
  -h, --hostname=""          指定容器的主机名
  -i, --interactive=false    打开STDIN,用于控制台交互
  -c, --cpu-shares=0         设置容器CPU权重,在CPU共享场景使用
  --cap-add=[]               添加权限,权限清单详见:http://linux.die.net/man/7/capabilities
  --cap-drop=[]              删除权限,权限清单详见:http://linux.die.net/man/7/capabilities
  --cidfile=""               运行容器后,在指定文件中写入容器PID值,一种典型的监控系统用法
  --cpuset=""                设置容器可以使用哪些CPU,此参数可以用来容器独占CPU
  -d, --detach=false         指定容器运行于前台还是后台 
      --device=[]                添加主机设备给容器,相当于设备直通
      --dns=[]                   指定容器的dns服务器
      --dns-search=[]            指定容器的dns搜索域名,写入到容器的/etc/resolv.conf文件
  -e, --env=[]               指定环境变量,容器中可以使用该环境变量
      --entrypoint=""            覆盖image的入口点
      --env-file=[]              指定环境变量文件,文件格式为每行一个环境变量
      --expose=[]                指定容器暴露的端口,即修改镜像的暴露端口
  --link=[]                  指定容器间的关联,使用其他容器的IP、env等信息
  --lxc-conf=[]              指定容器的配置文件,只有在指定--exec-driver=lxc时使用
  -m, --memory=""            指定容器的内存上限
  --name=""                  指定容器名字,后续可以通过名字进行容器管理,links特性需要使用名字
  --net="bridge"             容器网络设置,待详述
  --privileged=false         指定容器是否为特权容器,特权容器拥有所有的capabilities
  --restart=""               指定容器停止后的重启策略,待详述
  --rm=false                 指定容器停止后自动删除容器(不支持以docker run -d启动的容器)
  --sig-proxy=true           设置由代理接受并处理信号,但是SIGCHLD、SIGSTOP和SIGKILL不能被代理
  --volumes-from=[]          给容器挂载其他容器上的卷,挂载到容器的某个目录
  -w, --workdir=""           指定容器的工作目录
  
### 如实例
## 后台启动
docker run -d images
#### 启动并进入容器
docker run -i ubuntu:latest
### 从容器退出
exit
## 运行一个在后台执行的容器,同时,还能用控制台管理:
docker run -i -t -d ubuntu:latest
或 
docker run -it -d ubuntu:latest
## 运行一个带命令在后台不断执行的容器,不直接展示容器内部信息:
docker run -d ubuntu:latest ping www.docker.com
### 运行一个在后台不断执行的容器,同时带有命令,程序被终止后还能重启继续跑,还能用控制台管理,
docker run -d --restart=always ubuntu:latest ping www.docker.com
    ### --restart参数,支持三种逻辑实现:
        no:容器退出时不重启
        on-failure:容器故障退出(返回值非零)时重启
        always:容器退出时总是重启
## 为容器指定一个名字
docker run -d --name=ubuntu_server ubuntu:latest
### 容器暴露80端口,并指定宿主机80端口与其通信(: 之前是宿主机端口,之后是容器需暴露的端口),
docker run -d --name=ubuntu_server -p 80:80 ubuntu:latest
    ## 端口暴露:
    
###指定容器内目录与宿主机目录共享(: 之前是宿主机文件夹,之后是容器需共享的文件夹),
docker run -d --name=ubuntu_server -v /etc/www:/var/www ubuntu:latest

容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过 -P(大写) 或 -p (小写) 参数来指定端口映射。
(1)当使用 -P 标记时,Docker 会随机映射一个 49000~49900 的端口到内部容器开放的网络端口。
使用 docker ps 可以看到,本地主机的 49155 被映射到了容器的 50000 端口。此时访问本机的 49155 端口即可访问容器内 web 应用提供的界面。

$ sudo docker run -d -P training/webapp python app.py
 $ sudo docker ps -l
 CONTAINER ID  IMAGE                   COMMAND       CREATED        STATUS        PORTS                    NAMES
 bc533791f3f5  training/webapp:latest  python app.py 5 seconds ago  Up 2 seconds  0.0.0.0:49155->5000/tcp  nostalgic_morse

同样的,可以通过 docker logs 命令来查看应用的信息。

$ sudo docker logs -f nostalgic_morse
* Running on http://0.0.0.0:5000/
10.0.2.2 - - [23/May/2014 20:16:31] "GET / HTTP/1.1" 200 -
10.0.2.2 - - [23/May/2014 20:16:31] "GET /favicon.ico HTTP/1.1" 404 -

(2)-p(小写)则可以指定要映射的IP和端口,但是在一个指定端口上只可以绑定一个容器。支持的格式有

hostPort:containerPort、ip:hostPort:containerPort、 ip::containerPort。
  • hostPort:containerPort(映射所有接口地址)
    将本地的 5000 端口映射到容器的 5000 端口,可以执行如下命令:
$ sudo docker run -d -p 5000:5000 training/webapp python app.py #此时默认会绑定本地所有接口上的所有地址。
  • ip:hostPort:containerPort (映射指定地址的指定端口)
指定映射使用一个特定地址,比如 localhost 地址 127.0.0.1
$ sudo docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py
  • ip::containerPort (映射指定地址的任意端口)
绑定 localhost 的任意端口到容器的 5000 端口,本地主机会自动分配一个端口。
sudo docker run -d -p 127.0.0.1::5000 training/webapp python app.py

注意:
容器有自己的内部网络和 ip 地址(使用 docker inspect 可以获取所有的变量,Docker 还可以有一个可变的网络配置。)
-p 标记可以多次使用来绑定多个端口
例如

$ sudo docker run -d -p 5000:5000 -p 3000:80 training/webapp python app.py
  • -P也可以多端口暴露
[root@node-65 ~]# docker run --name h3 --rm --net bridge  -d -P --expose 2222 --expose 3333   docker.io/hehe/bbox:v0.1.1-httpd /bin/httpd -f -h /data/html

#查询映射端口
[root@node-65 ~]# docker port h3
2222/tcp -> 0.0.0.0:32772
3333/tcp -> 0.0.0.0:32771

2.3.2 查看运行的容器

docker ps  ## 仅显示当前正在运行的容器

### 可选参数
  -a, --all             ## 显示所有容器,含历史运行过的
  -n, --last int        Show n last created containers (includes all states) (default -1)
  -l, --latest          ### 查看最后一次运行的容器
  -q, --quiet          Only display container IDs

## 显示所有容器的ID
docker ps -aq

2.3.3 退出容器

exit ## 直接容器停止退出
Ctrl+P+Q  ## 容器不停止退出
2.3.4 删除容器
docker rm CONTAINER_ID   ## 删除指定容器   CONTAINER_ID: 容器id只要是能够证明唯一就可以,不用全输入
docker rm -f 容器id   ## 删除正在运行中的指定容器   
docker rm -f $(docker ps -aq)  #  删除所有容器
docker ps -aq|xargs docker rm  #  删除所有容器
docker rm -v 容器id ## 删除容器id时,宿主机的挂载目录仍存在,
## 没删掉,猜测那是因为还有其他容器在连着或者说是dbdata -v的时候创建的

2.3.5 启动容器

docker start 容器id

2.3.6 停止容器

docker stop 容器id
docker kill 容器id   ## 强制停止当前容器

2.3.7 重启容器

docker restart 容器id

2.3.8 查看容器日志

docker logs [OPTIONS] 容器ID

### 可选参数
Options:
      --details        Show extra details provided to logs
  -f, --follow         Follow log output
  -n, --tail string    Number of lines to show from the end of the logs (default "all")
  -t, --timestamps     Show timestamps

2.3.9 查看容器内部进程信息

docker top 容器id

## 测试
 yangxing@Albertz  ~  docker top 1a15faf2d140
UID        PID        PPID    ...     TIME                CMD
root       9701       9676            08:50           nginx: master process nginx -g daemon off;
uuidd      9763       9701    ...     08:50           nginx: worker process

2.3.10 查看镜像元数据

docker inspect 容器id

####  测试
 yangxing@Albertz  ~  docker inspect 1a15faf2d140
[
    {
        "Id": "1a15faf2d14070bf23c36be1a48c13d21b55787fba08ccc3380e1c629d3d5e97",
        "Created": "2021-05-16T08:50:52.5963216Z",
        "Path": "/docker-entrypoint.sh",
        "Args": [
            "nginx",
            "-g",
            "daemon off;"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 9701,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2021-05-16T08:50:52.8654351Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
        "Image": "sha256:f0b8a9a541369db503ff3b9d4fa6de561b300f7363920c2bff4577c6c24c5cf6",
        "ResolvConfPath": "/var/lib/docker/containers/1a15faf2d14070bf23c36be1a48c13d21b55787fba08ccc3380e1c629d3d5e97/resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/1a15faf2d14070bf23c36be1a48c13d21b55787fba08ccc3380e1c629d3d5e97/hostname",
        "HostsPath": "/var/lib/docker/containers/1a15faf2d14070bf23c36be1a48c13d21b55787fba08ccc3380e1c629d3d5e97/hosts",
        "LogPath": "/var/lib/docker/containers/1a15faf2d14070bf23c36be1a48c13d21b55787fba08ccc3380e1c629d3d5e97/1a15faf2d14070bf23c36be1a48c13d21b55787fba08ccc3380e1c629d3d5e97-json.log",
        "Name": "/dreamy_germain",
        "RestartCount": 0,
        "Driver": "overlay2",
        "Platform": "linux",
        "MountLabel": "",
        "ProcessLabel": "",
        "AppArmorProfile": "",
        "ExecIDs": null,
        "HostConfig": {
            "Binds": null,
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {}
            },
            "NetworkMode": "default",
            "PortBindings": {},
            "RestartPolicy": {
                "Name": "no",
                "MaximumRetryCount": 0
            },
            "AutoRemove": false,
            "VolumeDriver": "",
            "VolumesFrom": null,
            "CapAdd": null,
            "CapDrop": null,
            "CgroupnsMode": "host",
            "Dns": [],
            "DnsOptions": [],
            "DnsSearch": [],
            "ExtraHosts": null,
            "GroupAdd": null,
            "IpcMode": "private",
            "Cgroup": "",
            "Links": null,
            "OomScoreAdj": 0,
            "PidMode": "",
            "Privileged": false,
            "PublishAllPorts": false,
            "ReadonlyRootfs": false,
            "SecurityOpt": null,
            "UTSMode": "",
            "UsernsMode": "",
            "ShmSize": 67108864,
            "Runtime": "runc",
            "ConsoleSize": [
                0,
                0
            ],
            "Isolation": "",
            "CpuShares": 0,
            "Memory": 0,
            "NanoCpus": 0,
            "CgroupParent": "",
            "BlkioWeight": 0,
            "BlkioWeightDevice": [],
            "BlkioDeviceReadBps": null,
            "BlkioDeviceWriteBps": null,
            "BlkioDeviceReadIOps": null,
            "BlkioDeviceWriteIOps": null,
            "CpuPeriod": 0,
            "CpuQuota": 0,
            "CpuRealtimePeriod": 0,
            "CpuRealtimeRuntime": 0,
            "CpusetCpus": "",
            "CpusetMems": "",
            "Devices": [],
            "DeviceCgroupRules": null,
            "DeviceRequests": null,
            "KernelMemory": 0,
            "KernelMemoryTCP": 0,
            "MemoryReservation": 0,
            "MemorySwap": 0,
            "MemorySwappiness": null,
            "OomKillDisable": false,
            "PidsLimit": null,
            "Ulimits": null,
            "CpuCount": 0,
            "CpuPercent": 0,
            "IOMaximumIOps": 0,
            "IOMaximumBandwidth": 0,
            "MaskedPaths": [
                "/proc/asound",
                "/proc/acpi",
                "/proc/kcore",
                "/proc/keys",
                "/proc/latency_stats",
                "/proc/timer_list",
                "/proc/timer_stats",
                "/proc/sched_debug",
                "/proc/scsi",
                "/sys/firmware"
            ],
            "ReadonlyPaths": [
                "/proc/bus",
                "/proc/fs",
                "/proc/irq",
                "/proc/sys",
                "/proc/sysrq-trigger"
            ]
        },
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/a6da61278fc2cf9173d95ddf42423a7205c904149852777aab6ec5e5b50339a5-init/diff:/var/lib/docker/overlay2/ad35a12bf897131cc10be10d63d11a9582abbf634e2193320cfc2451cfa33167/diff:/var/lib/docker/overlay2/3c2b64ed6478e9e540f1dbef13d9a0a06a17a699bb0de63593383ddb1de21586/diff:/var/lib/docker/overlay2/f1e89d54b4a50f5dd26db5cc265ad3ffae9d5fadbcaf42706c9268b66bcdea9b/diff:/var/lib/docker/overlay2/0049b5dc1a961c5f1e5a09c705fec3d7310957610beb5b5fbf519282790924f6/diff:/var/lib/docker/overlay2/259f1acb2d082d1e55fcf8a53b016e2f5b3239be5020dbd1683e0de8ffe9d04f/diff:/var/lib/docker/overlay2/ba9bad3dd1459eb698032196d8d7af0fd95a7e8ee3b7958f47b635df53913041/diff",
                "MergedDir": "/var/lib/docker/overlay2/a6da61278fc2cf9173d95ddf42423a7205c904149852777aab6ec5e5b50339a5/merged",
                "UpperDir": "/var/lib/docker/overlay2/a6da61278fc2cf9173d95ddf42423a7205c904149852777aab6ec5e5b50339a5/diff",
                "WorkDir": "/var/lib/docker/overlay2/a6da61278fc2cf9173d95ddf42423a7205c904149852777aab6ec5e5b50339a5/work"
            },
            "Name": "overlay2"
        },
        "Mounts": [],
        "Config": {
            "Hostname": "1a15faf2d140",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "80/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "NGINX_VERSION=1.19.10",
                "NJS_VERSION=0.5.3",
                "PKG_RELEASE=1~buster"
            ],
            "Cmd": [
                "nginx",
                "-g",
                "daemon off;"
            ],
            "Image": "nginx",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": [
                "/docker-entrypoint.sh"
            ],
            "OnBuild": null,
            "Labels": {
                "maintainer": "NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e"
            },
            "StopSignal": "SIGQUIT"
        },
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "943f6b243c7ef8afdb218942abe4120da18532c1675ec3e88c1ddb9c2949e97a",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": {
                "80/tcp": null
            },
            "SandboxKey": "/var/run/docker/netns/943f6b243c7e",
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "0aa1fe1f0abe79293cd2c926dc7e72070abec13d6fb6d7f00c43e94a4359d2fa",
            "Gateway": "172.17.0.1",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "172.17.0.2",
            "IPPrefixLen": 16,
            "IPv6Gateway": "",
            "MacAddress": "02:42:ac:11:00:02",
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "258eaa95b91c2fba444368a1747c2f81497a775ad71952347a376bf24a673698",
                    "EndpointID": "0aa1fe1f0abe79293cd2c926dc7e72070abec13d6fb6d7f00c43e94a4359d2fa",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:02",
                    "DriverOpts": null
                }
            }
        }
    }
]
 yangxing@Albertz  ~ 

## 可选参数
Options:
  -f, --format string   Format the output using the given Go template
  
  ### 如:Get an instance’s log path
  $ docker inspect --format='{{.LogPath}}' $INSTANCE_ID

2.3.11 进入当前正在运行的容器

方式一:docker exec
docker exec [options] CONTAINER COMMAND [ARG...]
    CONTAINER:容器名称或ID,必选,看出exec操作的对象是容器
    COMMAND:命令,必选
官方命令解释:Run a command in a running container,翻译过来就是在一个正在运行的容器中执行命令,
exec是针对已运行的容器实例进行操作,在已运行的容器中执行命令,不创建和启动新的容器。

#### 进入容器终端并且的保留为容器终端的输入形式(-it和bash的结合作用)
docker exec -it 容器id  bash  

## 测试
yangxing@Albertz  ~  docker exec -it 1a bash       ###  观察用户和用户组
root@1a15faf2d140:/#                                 ### 已经进入容器内部

#### 非-it ....bash 的组合
docker exec 容器id  command   ### command 具体的指令

#### 测试
 yangxing@Albertz  ~  docker exec -it 1a ls
bin   docker-entrypoint.d   home   media  proc	sbin  tmp
boot  docker-entrypoint.sh  lib    mnt	  root	srv   usr
dev   etc		    lib64  opt	  run	sys   var
 yangxing@Albertz  ~  docker exec 1a bash
 yangxing@Albertz  ~  docker exec 1a ls
bin
boot
dev
docker-entrypoint.d
docker-entrypoint.sh
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
 yangxing@Albertz  ~ 
#### 从测试上可以看出非-it ....bash 的组合并没有进入容器内部,相当于在容器外部执行了命令操作
# docker exec -it nginx-3 /bin/bash  // 直接进入容器nginx-3,执行bash
# docker exec nginx-3 bash -c "ls"   // 在容器中执行命令 ls,输出结果

方式二:docker attach
 docker attach [OPTIONS] CONTAINER
    CONTAINER:容器名称或ID,必选,attach的操作对象也是容器
官方命令解释:Attach local standard input, output, and error streams to a running container,
翻译过来,将本机的标准输入(键盘)、标准输出(屏幕)、错误输出(屏幕)附加到一个运行的容器,
也就是说本机的输入直接输到容器中,容器的输出会直接显示在本机的屏幕上。

####  测试
yangxing@Albertz  ~  docker run -it nginx bash
root@470cabe05fa4:/# %          ###  ctrl+P+Q 不停止容器退出                                                                                          yangxing@Albertz  ~  docker attach 8db
yangxing@Albertz  ~  docker attach 470   
root@470cabe05fa4:/#

可以看出Docker attach可以attach到一个已经运行的容器的stdin,然后进行命令执行的动作。
但是需要注意的是,如果从这个stdin中exit,会导致容器的停止。

2.3.12 从容器内拷贝文件到主机上

docker cp 容器id:容器内路径  目的主机路径

### 测试
yangxing@Albertz  ~  docker run -it nginx bash   ## 启动并进入容器
root@bb4ffc770d26:/# touch ~/test.java             ## 创建文件
root@bb4ffc770d26:/# ls -l ~/
total 0
-rw-r--r-- 1 root root 0 May 16 16:18 test.java
root@bb4ffc770d26:/# %
yangxing@Albertz  ~  docker cp bb4:/root/test.java ./testmp/   ### copy文件
 yangxing@Albertz  ~  ls ./testmp/
test.java
 yangxing@Albertz  ~ 

三、采坑记录

3.1 docker访问出现Permission denied的解决办法

有以下方式解决权限问题:
1.在运行容器的时候,给容器加特权,及加上 --privileged=true 参数:

docker run -i -t -v /usr/local/app:/usr/local/app --privileged=true 镜像id /bin/bash

2.运行中的容器可通过以下命令

sudo docker exec -ti -u root 848669a8722b bash

3.2 configure: error: no acceptable C compiler found in $PATH 问题解决

linux中安装Python

wget https://www.python.org/ftp/python/3.8.1/Python-3.8.1.tgz

解压源码包:

tar -zxvf Python-3.8.1.tgz

编译
使用 make 工具进行编译

./configure  --prefix=/usr/local
./configure --prefix=/usr/local/python/ --with-openssl=/usr/bin/openssl
apt install make
make&&sudo make install

在docker中安装Python报错

root@00079951fae9:/Python-3.8.2# ./configure --prefix=/usr/local/
configure: error: in `/Python-3.8.2':
configure: error: no acceptable C compiler found in $PATH
See `config.log' for more details

解决方案:
安装GCC软件套件

apt install gcc

遇到的其他问题

apt install sudo
apt install zlib*
apt install make

3.3 python3.7.0以上版本pip安装时报错ModuleNotFoundError: No module named ‘_ctypes‘的解决办法

解决方案:
3.7版本以上需要一个新的包libffi-devel,安装此包之后再次进行编译安装即可

#apt install libffi-dev
#make install

ubuntu安装Chrome:

##对于谷歌Chrome32位版本,使用如下链接:
wget https://dl.google.com/linux/direct/google-chrome-stable_current_i386.deb
##对于64位版本可以使用如下链接下载:
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb 
## 下载完后,运行如下命令安装。
sudo dpkg -i google-chrome*
sudo apt-get -f install 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值