1. 什么是Docker

  Docker是一种虚拟化技术,其在容器的基础上进一步封装了文件系统、网络互联、进程隔离等等,从而极大地简化了容器的创建和维护。Docker使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的 cgroup,namespace,以及 AUFS 类的 Union FS 等技术,实现进程隔离、文件系统隔离、网络隔离等。Docker 底层的核心技术包括 Linux 上的命名空间(Namespaces,如pid namespace、mount namespace、network namespace等分别用来隔离进程、文件系统、网络)、控制组(Control groups)、Union 文件系统(Union file systems)和容器格式(Container format)。

Docker很重要的作用之一:隔离进程——Docker容器内的进程无法看到宿主机以及其他Docker容器内的进程,这样可以防止一个进程被入侵了来恶意访问或破坏其他非本Docker容器内的进程。

Docker和传统虚拟机的区别:传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。如下图展示了其区别:

           

Docker口号:Build、Ship、Run;Build Once、Run Anywhere.

2. Docker四个核心概念

镜像Image:

对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像(Image),就相当于是一个 root 文件系统。除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。

容器Container:容器是以镜像为基础,再加一层容器存储层,组成多层存储结构去运行的。

镜像( Image )和容器( Container )的关系,就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。容器实质是个进程。

容器和镜像的区别就在于,所有的镜像都是只读的,而每一个容器其实等于镜像加上一个可读写的层,也就是同一个镜像可以对应多个容器

仓库Repository:就是一个集中存储、分发镜像的服务,类似于Maven的Repository。

注册服务器Registry:管理仓库Repository的具体服务器。每个服务器上可以有多个仓库,每个仓库下面有多个镜像。Docker Hub、DaoCloud等就是Registry,也可以在本地搭建Registry供团队间分享镜像,公司内部通常有自己的Registry。内网搭建Registry可参阅:https://www.cnblogs.com/sparkdev/p/6890995.html

3. Docker架构

Docker 采用了 C/S 架构,包括客户端和服务端。客户端和服务端既可以运行在一个机器上,也可通过 socket 或者 RESTful API 来进行通信。

  • Docker 守护进程 (Daemon)作为服务端接受来自客户端的请求,并处理这些请求(创建、运行、分发容器)。Docker 守护进程一般在宿主主机后台运行。
  • Docker 客户端则为用户提供一系列可执行命令,用户用这些命令实现跟 Docker 守护进程交互。

Docker 镜像(Images)

Docker 镜像是用于创建 Docker 容器的模板。

Docker 容器(Container)

容器是独立运行的一个或一组应用。

Docker 客户端(Client)

Docker 客户端通过命令行或者其他工具使用 Docker API (https://docs.docker.com/reference/api/docker_remote_api) 与 Docker 的守护进程通信。

Docker 主机(Host)

一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。

Docker 仓库(Registry)

Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。

Docker Hub(https://hub.docker.com) 提供了庞大的镜像集合供使用。

由于采用CS架构,因此可以通过修改环境变量DOCKER_HOST来操作远程的Docker Daemon,默认是localhost

4. 安装Docker

Docker分为社区版CE和企业版EE,前者免费。不同OS及同中OS的不同版本上安装Docker的方法各异,详见 Docker安装

这里以Ubuntu16.04为例。

1. apt源使用HTTPS以确保软件下载过程中不被篡改,故先安装HTTPS传输及CA证书相关:

sudo apt-get update
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
software-properties-common

2. 为确认所下载软件包的合法性,需添加软件源的GPG密钥: curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add -

3. 向/etc/apt/source.list添加Docker软件源:

sudo add-apt-repository \
"deb [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu \
$(lsb_release -cs) \
stable"

4. 更新apt软件包缓存,安装Docker CE: sudo apt-get update && sudo apt-get install docker-ce

5. 启动Docker CE:

设置docker服务开机启动: sudo systemctl enable docker

启动docker服务: sudo systemctl start docker

6. 测试是否正确安装: sudo docker run hello-world ,该命令首次运行会从Docker仓库下载hello-world镜像,输出如下信息表示运行成功:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
ca4f61b1923c: Pull complete
Digest: sha256:be0cd392e45be79ffeffa6b05338b98ebb16c87b255f48e297ec7f98e123905c
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:
. The Docker client contacted the Docker daemon.
. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
. 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://cloud.docker.com/ For more examples and ideas, visit:
https://docs.docker.com/engine/userguide/

7. 由于国内网络原因,拉取镜像会很慢,可以设置为国内仓库服务器:在 /etc/docker/daemon.json 添加如下内容(若文件不存在则先建之):

{
"registry-mirrors": [
"https://registry.docker-cn.com"
]
}

然后重启服务:

sudo systemctl daemon-reload
sudo systemctl restart docker

执行 docker info ,若看到 Registry Mirrors: https://registry.docker-cn.com/ 则说明修改成功。

8、普通用户加入Docker用户组

默认情况下,docker 命令会使用 Unix socket 与 Docker 引擎通讯。而只有 root 用户和 docker 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 root 用户。因此,更好地做法是将需要使用 docker 的用户加入 docker 用户组。

建立Docker用户组(一般安装完Docker后就默认创建了): $ sudo groupadd docker

将普通用户加入Docker用户组: $ sudo usermod -aG docker $USER

退出当前终端并重新登录即可。

5. 镜像与容器操作

各命令具体用法可以通过 docker 命令 --help 或 docker help you_command 查看。下面稍做总结。

5.0 查找与推送镜像

查找示例: docker search centos ,会从镜像仓库中查找指定镜像。

推送示例: docker push yourhubusername/ubuntu:16.04 ,会推送到镜像服务器。不过需要事先注册镜像服务器账号,且只能传到自己账号的下面,因此需将待传的镜像标签改为账号前缀。详见:https://ropenscilabs.github.io/r-docker-tutorial/04-Dockerhub.html

5.1 获取镜像

命令格式: docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签] ,如 $ docker pull ubuntu:16.04 。不写标签的话默认是latest,即最新版。在 Docker 1.13+ 版本中推荐使用 docker image 来管理镜像。

此外,也可以通过 docker import 导入容器,会创建相应的镜像。

具体可参见:获取镜像

5.2 容器创建与容器操作

运行示例: $ docker run -it --rm ubuntu:16.04 bash ,表示在终端交互式操作、容器退出后删除掉,运行的是linux下的bash,若不指定运行的命令则默认运行/bin/bash;可以指定运行其他命令,如: docker run -it ubuntu cat /etc/os-release

另一示例:  docker run --label version=1.5 --name zsmserver -d -p : nginx , #从镜像nginx启动名为zsmserver的容器,监听80短口,若镜像不存在则会下载 。-p参数指定本机端口映射到容器的哪些端口,其本质是在宿主机iptable的nat表添加相应的规则;--label用于给容器加一些metadata如license、vendor等。通过docker inspect 可以看到这些信息。

当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:

  • 检查本地是否存在指定的镜像,不存在就从公有仓库下载
  • 利用镜像创建并启动一个容器
  • 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
  • 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
  • 从地址池配置一个 ip 地址给容器
  • 执行用户指定的应用程序
  • 执行完毕后容器被终止

注:容器隔离指的是容器之间互相看不到对方容器内的进程。容器启动后,在宿主机 ps 是可以看到容器内的进程的。

5.2.1 关于后台运行

可以通过-d参数让Docker在后台运行(守护态运行)而不是把命令执行结果输出到当前宿主主机下(即这里的后台是相对于宿主主机而言的);但是,容器是否会长久运行,是和 docker run 指定的命令有关,和 -d 参数无关。

当以后台模式(-d)运行 run Dokcer容器时,容器内必须有一个前台进程(如top、tail等)否则容器就会结束退出(如service nginx start默认以后台模式运行),因为Docker觉得没事可做了。其本质原因是:docker 容器默认会把容器内部第一个进程,也就是pid=1的程序作为docker容器是否正在运行的依据,若该程序结束了则认为docker 容器挂了,从而docker容器便会直接退出,docker run的时候正是把command作为容器内部命令。

因此,要想避免容器运行就退出,可以在多个命令的最后加上top等命令,如  service nginx start && top  。详见:https://blog.csdn.net/meegomeego/article/details/50707532

5.2.2 其他容器操作命令

docker help your_command  或 docker your_command --help  #查看命令详细用法

docker cp    xxx    xxx                  #files/folders between a container and the local filesystem
docker run 镜像名 [运行的程序命令及参数] #从镜像新建容器并启动,若镜像不存在则先下载
docker ps -a #查看所有容器,包括正在运行的、已终止的等
docker container ls #查看正在运行的容器
docker [container] start/stop/restart containerid_or_name #启动/停止/重启 已存在的容器
docker logs containerid_or_name #获取后台运行的容器的输出
docker exec -it containerid 命令 #重新进入后台运行的容器,也可以通过命令 docker attach containerid 进入,但这种方式下进入再退出时容器会跟着终止,所以推荐用exec方式。
docker inspect containerid_or_name     #查看容器信息。为了查看容器ip,可以:docker inspect --format '{{ .NetworkSettings.IPAddress }}' containerid_or_name docker [container] rm [-f -v] containerid #删除指定的容器。-f参数可删除运行中的容器 Docker会发SIGKILL信号给容器、-v参数删除容器关联的数据卷
docker container prune #删除所有终止状态的容器,也可以 docker rm $(docker ps -aq),括号里的命令功能是获取所有container的id docker tag src_image_name new_image_name #为镜像重命名,如docker tag ubuntu:1.0 zsm/ubuntu:1.0 docker network create/connnect/disconnect/inspect/ls/prune/rm #网络管理

更多命令guide可参阅官方文档:https://docs.docker.com/engine/reference/run/

5.3 查看镜像

列出已下载镜像: docker images 或 docker image ls 。

  • 显示的镜像大小是解压后的,官网Docker Hub上显示的则是压缩了的;
  • 由于镜像是分层存储的,可以继承使用,因此总占用空间并不是各镜像占用大小的和;
  • 该命令列出顶层镜像,如果要列出包括中间层镜像的所有,可以加-a参数。可以看出,这里的ls跟Linux下的ls命令用法类似,可以部分匹配等、还支持强大的过滤器参数,具体可输入help命令查看。如列出redis镜像的镜像ID: docker image ls -q redis

查看镜像、容器、数据卷所占用的空间: docker system df ,结果如下:

5.4 移除虚悬镜像

命令: docker image prune

实际上,可以通过 docker image/container/volumn/network prune 来删除不再被使用的镜像/容器/卷/网络,亦可用命令 docker system prune 一次移除这四者

5.5 删除本地镜像

命令: docker image rm [选项] <镜像1> [<镜像2> ...] 或 docker rmi [选项] <镜像1> [<镜像2> ...] ,这里的镜像标识可以是ID、镜像名、镜像摘要等。

  • 删除镜像时实际上是在要求删除某个标签的镜像,由于一个镜像可对应多个标签,因此若删除指定标签的镜像后仍有其他标签指向该镜像则实际上不会删除该镜像,而只是标记指定标签被删除。
  • 同样地,对于要删除的镜像,若有从该镜像启动的容器存在,则即使容器没在运行,该镜像也不会被删除。

5.6 定制docker镜像

可以通过commit命令(具体参见利用-commit-理解镜像构成),但是不推荐,因为会产生不必要的修改使得镜像臃肿;

通常通过Dockerfile定制镜像。文件Dockerfile的内容示例如下:

 FROM debian:jessie
RUN buildDeps='gcc libc6-dev make' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components= \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps

RUN指令用来执行命令行命令:

  • 可以是shell 格式: RUN <命令> ,像直接在命令行输入命令一样,如 RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html ;
  • 也可以是exec 格式: RUN ["可执行文件", "参数1", "参数2"] ,像是函数调用中的格式。

Dockerfile 中每一个指令都会建立一层新存储层, RUN指令也不例外(即每一个RUN指令都对应一个新容器。将包括如下指令的Dockerfile构建运行后会提示找不到txt文件就是因为这个原因,若在中间加上WORKDIR则没问题),并且Docker中有层数的限制。比较好的做法是用一个RUN指令和多个&&将多个命令连接起来。

RUN cd /app
# WORKDIR /app
RUN echo "hello" > world.txt

此外,这组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的 软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。这是很重要的一步,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。 因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。

5.7 构建镜像

(具体可参见 镜像构建上下文 )

写了上节所述的Dockerfile后,执行命令生成Docker镜像: docker build [选项] <上下文路径/URL/-> 。假设Dockerfile在./dockertest/下,则命令为: Docker build -t zsmserver ./dockertest

  • docker build的工作原理:Docker在运行时分为 Docker 引擎(即服务端守护进程)和客户端工具。Docker 引擎提供了一组 REST API,被称为 Docker Remote API,而如 docker 命令这样的客户端工具则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,docker build 命令构建镜像其实并非在本地构建,虽表面上像是在本机执行各种 docker 功能,但实际上一切都是使用的远程调用形式在服务端(Docker 引擎)完成。
  • 关于上下文路径:进行镜像构建时并非所有定制都会通过 RUN 指令完成,经常需将一些本地文件复制进镜像,如通过 COPY 指令、ADD 指令等,这些指令指定的路径参数以给定的上下文路径作为根目录。
  • Docker构建实际上是把当前上下文下的文件打包上传到服务端,在服务端进行构建,服务端以上传上来的该文件夹为上下文(即根目录)
  • 一般将Dockerfile置于空目录或项目根目录,若不希望目录下的一些东西构建时传给Docker引擎,可以用与Git的.gitignore一样的语法写个.dockerignore文件加以剔除。
  • 构建命令中并没有指定Dockerfile的名字,服务端构建时默认会查找名字为“Dockerfile”的文件进行构建,也可以通过-f参数指定Dockerfile文件。如: docker build -t zsmnginx -f MyDockerFile2 .

其他构建方式(详见其他Docker Build方法):

  • 直接用 Git repo 进行构建,要求repo内有Dockerfile
  • 用给定的 tar 压缩包构建,要求压缩包内有Dockerfile
  • 从标准输入中读取 Dockerfile 进行构建
  • 从标准输入中读取上下文压缩包进行构建等

构建镜像应使得镜像尽可能小。方法有:用一个RUN指令和多个&&将多个命令连接起来外、多阶段构建、选择Alpine作为基础镜像等。可参阅:https://www.cnblogs.com/sparkdev/p/8594602.html

5.8容器和镜像的保存与恢复

(更多可参考 https://jingsam.github.io/2017/08/26/docker-save-and-docker-export.html

docker save / load 用于保存与恢复镜像

save时参数可以是镜像或容器,参数为容器时实际保存的仍是容器对应的镜像而不是容器

可以指定多个镜像名以将多个镜像保存到一个压缩文件中

适用场景:如要部署的客户服务器不能连外网,此时可以先将若干个镜像打包,再复制到客户服务器上并通过 docker load 导入。

示例:

docker save -o images.tar postgres:9.6 mongo:3.4  # 将两个镜像打包为images.tar文件。亦可 docker save postgres:9.6 mongo:3.4 > images.tar
docker load -i images.tar # 从文件中恢复镜像

docker export / import 用于将容器(的文件系统)打包为文件、从文件恢复为一个镜像

import时可以为镜像指定新名称

适用场景:制作基础镜像。如从一个ubuntu镜像启动一个容器,然后安装一些软件和进行一些设置后,使用docker export保存为一个基础镜像,就可把这个镜像分发给其他人使用,如作为基础的开发环境。

示例:

docker export -o zsmredis.tar redis_sensestudy  # 将容器打包成tar文件。亦可 docker export redis_sensestudy > zsmredis.tar
docker import zsmredis.tar myredis:latest # 从文件导入为镜像 docker import saved_container_filename_or_url_image_name

:docker load 和 docker import 的区别在于后者将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。

6. Dockerfile指令

(详见Dockerfile指令详解

在一个Dockerfile中,CMD、ENTRYPOINT、HEALTHCHECK等只可出现一次,若出现多次则只有最后一次生效。

6.1 COPY

格式: COPY <源路径>... <目标路径> 或 COPY ["<源路径1>",... "<目标路径>"] 。如: COPY package.json zh* /usr/src/app/

作用:从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置,复制时源文件的各种元数据都会保留。

注意:若被复制的文件是个目录,则不会复制该目录自身,只会复制其下的文件或目录,如  COPY ./* ./ ,若 ./ 下有doc目录,则只会复制doc下的文件或目录而不会复制doc。解决:改为 copy . ./

6.2 ADD

与COPY类似,只不过源文件可以为URL或压缩包,会自动下载或解压。推荐优先使用COPY

6.3 CMD

作用:指定容器启动时的默认执行程序。上面示例 docker run -it ubuntu cat /etc/os-release 指定启动时显示系统信息,该默认操作可通过Dockerfile里的CMD指令指定(注:若在启动容器时指定默认执行程序,则会覆盖Dockerfile里CMD指定的

格式: shell 格式:CMD <命令> 或 exec 格式:CMD ["可执行文件", "参数1", "参数2"...] 。如: CMD cat /etc/os-release ,第一种格式实际上会被包装为 sh -c 的参数 的形式执行,即: CMD [ "sh", "-c", "cat /etc/os-release" ] 。注:

1、通常用exec格式的命令。

2、两种命令的本质区别:exec格式指定的命令在容器内的进程号为1,而shell格式指定的则不是(此时进程号为1的进程为sh进程)

Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样, 用 upstart/systemd 去启动后台服务,容器内没有后台服务的概念。如 CMD service nginx start 并不会使得容器一直在运行,其实际上会被理解为 CMD [ "sh", "-c", "service nginx start"] ,此时主进程实际上是 sh。则当 service nginx start 命令结束后,sh 也就结束了,sh 作为主进程退出了,自然就会令容器退出。正确做法是直接以前台形式执行nginx: CMD ["nginx", "-g", "daemon off;"]

6.4 ENTRYPOINT

目的和 CMD 一样,都是在指定容器启动时执行的默认程序及参数,命令格式也一样。不同的是ENTRYPOINT功能更强大,使用ENTRYPOINT指定的默认程序,可以接收在启动容器时给定的参数,从而使容器像个可执行程序一样。

应用场景1:让镜像如一个命令般可执行。”An ENTRYPOINT allows you to configure a container that will run as an executable.“

示例如下,功能为显示当前的公网IP:

FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/* #CMD [ "curl", "-s", "http://ip.cn" ]
ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]

经构建 docker build -t myip . 后,若Dockerfile中使用CMD则只能 docker run myip 运行,无法为curl指定其他参数;若用ENTRYPOINT则可以 docker run myip -i 为curl进一步指定参数(-i表示curl结果输出响应头)。

从上面可见CMD和ENTRYPOINT的区别:后者在运行容器时指定的参数(即-i)会拼接到ENTRYPOINT指定的命令上(变成curl -s http://ip.cn -i);前者则不会拼接而是替换,即把 -i 当成默认命令替换掉 curl -s http//ip.cn 进行执行,从而报错。

另一示例: ENTRYPOINT ["/bin/echo"] ,假设build成镜像echoimage,则可以执行 docker run -it imageecho “this is a test” 就会输出字符串,使得镜像像个echo可执行程序。

应用场景2:应用运行前的准备工作。

借助ENTRYPOINT指定一个脚本文件作为默认程序,完成系统配置、初始化等预处理工作。由于启动docker时指定的参数会拼接到ENTRYPOINT指定的默认程序后,因此参数被当成脚本的参数,脚本里可以根据这些参数作一些处理工作。用CMD则无法达到此功能。

注:

1、对于ENTRYPOINT只有为exec格式时默认命令才不会被创建容器时指定的参数覆盖而是附加,shell格式则照样会被覆盖。对于exec格式,也可以在创建容器时通过--entrypoint command覆盖默认命令,如 docker run -it --entrypoint pwd imageecho

2、一个Dockerfile中CMD和ENTRYPOINT须至少出现一个,其组合效果如下:

6.5 ENV

设置环境变量,格式为: ENV <key> <value>  或 ENV <key1>=<value1> <key2>=<value2>... ,Docker里后面的指令如RUN以及运行时的应用都可以用此定义的变量,引用方式示例:$filename。

6.6 ARG

与ENV类似,定义环境变量及默认值。不同的是定义的环境变量在容器运行时不存在;此外,该默认值可在构建命令 docker build 中用 --build-arg <参数名>=<值> 来覆盖。

6.7 VOLUMN

定义匿名卷及其挂载位置,格式为: VOLUME ["<路径1>", "<路径2>"...]  或 VOLUME <路径> ,如 VOLUMN /data ,容器运行时会自动将匿名卷挂载到指定目录。可在容器运行时指定数据卷的位置,覆盖匿名卷设置,如 docker run -d -v mydata:/data echo 将本机的命名卷mydata挂载到容器内的目录 /data 下。

注:

容器运行时应尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中。定义匿名卷后,Docker容器启动时会将指定的路径挂载为匿名卷,这样向该路径写入的数据不会容器存储层,从而保证容器存储层无状态化。

对于 匿名卷 和 非绝对路径的 命名卷,默认都存于本机的 /var/lib/docker/volumes/ 下。对于匿名卷,会在宿主机上的该目录下随机产生一个对应的子目录。

可以通过命令 docker inspect --type container -f '{{range $i, $v := .Mounts }}{{printf "%v\n" $v}}{{end}}' $containerName 查看容器的卷信息。

更多相关见下文的 数据卷操作 一节。

6.8 EXPOSE

格式: EXPOSE <端口1> [<端口2>...] 。作用:声明容器运行时可以使用的端口。这只是声明容器打算使用的端口,在运行时并不会因为这个声明应用就会开启指定的端口,容器启动时通过 -p <宿主端口>:<容器端口> 可启用对应端口。

6.9 RUN

见5.6节

6.10 WORKDIR

格式: WORKDIR <工作目录路径> 。作用:指定工作目录(或称为当前目录),以后各层的当前目录就被改为指定的目录,若该目录不存在,WORKDIR 会建立该目录。

6.11 USER

格式: USER <用户名> 。作用:与WORKDIR类似,切换为指定的用户,改变之后层执行 RUNCMD 以及 ENTRYPOINT 这类命令的身份。用户需要事先存在。

6.12 HEALTHCHECK

作用:周期性运行给定的命令以判断容器主程序是否正常运行。详见 healthcheck-健康检查。格式如下:

HEALTHCHECK [选项] CMD <命令> :设置检查容器健康状况的命令

HEALTHCHECK NONE :如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令

6.13 ONBUILD

格式: ONBUILD <其它Dockerfile指令> 。作用:用来设定触发器,在当前镜像build时不会执行ONBUILD指定的指令,只有在当前镜像的直接子镜像(即不包括孙镜像)build时才会执行ONBUILD指定的指令。

ONBUILD指令相当于创建一个模板镜像,后续可根据该模板镜像创建不同的特定子镜像,需要在子镜像构建过程中执行的一些通用操作就可以在模板镜像对应的Dockerfile文件中用ONBUILD指令指定,从而减少dockerfile文件的重复内容编写。

示例:

Dockerfile1:

FROM ubuntu
ONBUILD RUN mkdir mydir

构建为onbuild_image1: docker build -t onbuild_image -f Dockerfile1 . ,此时不会执行ONBUILD指定的指令。

Dockerfile2:

FROM onbuild_image1
CMD echo

构建为onbuild_image2: docker build -t onbuild_image2 -f Dockerfile2 . ,此时会执行ONBUILD指定的指令。如下:

分别查看是否有mydir目录: docker run --rm onbuild_image2 ls |grep mydir ,可以得到onbuild_image2有mydir目录而onbuild_image1没有。

6.14 多阶段构建

作用:从Docker 17.05.0-ce版本起,官方提供了简便的多阶段构建(multi-stage build) 方案,允许在同一Dockerfile中书写多阶段构建指令。

示例:

关键在于AS关键字和 --from 选项。也可以不用AS关键字,此时在from关键字后不是阶段的名字而是阶段的下标,按出现顺序从0起)

FROM muninn/glide:alpine AS build-env # AS关键字给此阶段起名
ADD . /go/src/app
WORKDIR /go/src/app
RUN glide install
RUN go build -v -o /go/src/app/app-server FROM alpine
RUN apk add -U tzdata
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
COPY --from=build-env /go/src/app/app-server /usr/local/bin/app-server # --from 指定从指定阶段的镜像复制文件
EXPOSE 80
CMD ["app-server"]

在多阶段构建的支持下,可以在build时加上  --target 阶段名 选项来只构建某阶段的镜像,如: docker build --target build-env -t go/helloworld:v1 .

此外,借助多阶段构建,可以将多个项目的二进制文件集成到一个镜像里,伪码示例如下:

from debian as build-essential
arg APT_MIRROR
run apt-get update
run apt-get install -y make gcc
workdir /src from build-essential as foo
copy src1 .
run make from build-essential as bar
copy src2 .
run make from alpine
copy --from=foo bin1 .
copy --from=bar bin2 .
cmd ...

7. 私有仓库

Docker Hub、DaoCloud等是公共的Registry,人们可以在上面创建Repository。有时候公共仓库不方便或者网络环境差,可以搭建私有仓库供私人或团队使用。构建私有镜像仓库可以借助官方工具docker-registry。详见 私有仓库

8. 数据卷与挂载

数据卷的作用在于把宿主机上的指定目录挂载到容器内的某个位置(目录)上,即把宿主机的指定目录共享给容器,因此容器内对该目录的操作会反应到宿主机,从而操作永久有效,即使容器被删除了。

在Docker中管理数据的方式有两种:挂载数据卷(Volumes)和挂载主机目录(Bind mounts)。前者本质上就是后者,因为创建数据卷就是在本机上创建一个目录(默认在本机的 /var/lib/docker/volumes/ 下创建目录)并此后将该目录关联到容器内的指定目录。这两种方式都使得可以在主机和容器间共享文件夹或文件。

数据卷:

数据卷是一个可供一个或多个容器使用的特殊目录,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的 数据卷

数据卷绕过 UFS,可以提供很多有用的特性:

  • 数据卷可以在容器之间共享和重用
  • 对数据卷的修改会立马生效
  • 对数据卷的更新,不会影响镜像
  • 数据卷默认会一直存在,即使容器被删除

数据卷操作:

docker volumn create   vol_name  #创建数据卷,名字中的字符只能是[a-zA-Z0-9_.-]
docker volumn ls #查看所有数据卷
docker volumn inspect vol_name #查看指定数据卷
docker volumn rm vol_name #删除指定数据卷
docker volumn prune #删除无主数据卷

数据卷 是被设计用来持久化数据的,其生命周期独立于容器,Docker不会在容器被删除后自动删除数据卷,也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷。若需要在删除容器的同时移除数据卷,可在删除容器时加 -v 参数。

挂载操作:将本机上的目录或创建的数据卷挂载到容器内的指定路径上,两种:一种是用 --mount 参数、另一种是用-v(或--volumn)参数,大同小异。数据卷可以不用事先创建,指定挂载时若数据卷不存在则会创建之。

法1: -v vol_name_or_absolute_path:target_dir ,对于挂载数据卷或挂载本机目录命令一样,如 -v my-vol:/webapp 或 -v /home/zsm/data:/webapp ,卷或源目录不存在时会创建。

法2: --mount [type=bind] source=vol_name_orabsolute_path,target=target_dir [,readonly] 。卷不存在时会创建、源目录不存在时会报错。当挂载本机目录时需要 type=bind 选项,当启用readonly时在容器内的挂载目录内只有读权限。示例:

docker run -d -p : \
--name web \
# -v my-vol:/wepapp \
--mount source=my-vol,target=/webapp \
#--mount type=bind source=/home/zsm/data,target=/webapp,readonly\
training/webapp python app.py

通过命令 docker inspect web 查看容器信息,数据卷信息在“Mounts”key下,可见my-vol被创建在了 /var/lib/docker/volumns/ 下:

"Mounts": [
{
"Type": "volume",
"Name": "my-vol",
"Source": "/var/lib/docker/volumes/my-vol/_data",
"Destination": "/webapp",
"Driver": "local",
"Mode": "z",
"RW": true,
"Propagation": ""
}
]

也可以将主机的一个文件挂载到容器中,示例如下(下例使得主机可以记录在容器中输入过的命令):

docker run --rm -it \
# -v $HOME/.bash_history:/root/.bash_history \
--mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \
ubuntu:17.10 \
bash 

注意:在docker run 等命令中指定挂载路径时,若路径含有空格则会因被当成多个参数而出错,故最好将路径用引号括起来。如 -v "$logDirInHost":"$logDirInContainer"

查看数据卷信息(包括匿名卷): docker inspect --type container -f '{{range $i, $v := .Mounts }}{{printf "%v\n" $v}}{{end}}' $containerName ,结果示例:

{bind  /etc/hosts /etc/hosts  rw true rprivate}
{volume 62a13e0bc4a63a88de20a2c6c33dc347f542ebdfcbac5278fa082dbf790c3bff /var/lib/docker/volumes/62a13e0bc4a63a88de20a2c6c33dc347f542ebdfcbac5278fa082dbf790c3bff/_data /var/lib/mysql local true }

9. 进阶_Docker网络

(其他可参阅 使用网络高级网络设置Docker网络实现

9.1 原理

Docker利用Linux的namespace来实现资源隔离,如pid namespace、mount namespace、network namespace分别用来隔离进程、文件系统、网络。Docker 的网络实现就是利用了 Linux 上的network namespace和虚拟网络设备(特别是 veth pair)。

首先,要实现网络通信,机器需要至少一个网络接口(物理接口或虚拟接口)来收发数据包;此外,如果不同子网之间要进行通信,需要路由机制。

Docker 中的网络接口默认都是虚拟的接口。虚拟接口的优势之一是转发效率较高。 Linux 通过在内核中进行数据复制来实现虚拟接口之间的数据转发,发送接口的发送缓存中的数据包被直接复制到接收接口的接收缓存中。对于本地系统和容器内系统看来就像一个正常的以太网卡,只是它不需要真正同外部网络设备通信,速度要快很多。

Docker 容器网络就利用了这项技术,它在本地主机和容器内分别创建一个虚拟接口,并让它们彼此连通(这样的一对接口叫做 veth pair)。

Docker创建一个容器时,会进行如下与网络相关的操作

  1. 创建一对虚拟接口,分别放到本地主机和新容器中;
  2. 本地主机一端桥接到默认的 docker0 或指定网桥上,并具有一个唯一的名字,如 veth65f9;
  3. 容器一端放到新容器中,并修改名字作为 eth0,这个接口只在容器的命名空间可见;
  4. 从网桥可用地址段中获取一个空闲地址分配给容器的 eth0,并配置默认路由到桥接网卡 veth65f9。

完成这些之后,容器就可以使用 eth0 虚拟网卡来连接其他容器和其他网络。

9.2 网络设置

可在 docker run 时通过 --net 参数来指定容器的网络配置(若在docker-compose中配置则用 "network-mode: xxx "),有4个可选值:

  1. bridge模式:--net=brige,默认值,连接到默认的网桥(即docker0)
  2. host模式:--net=host
  3. container模式:--net=container:NAME_or_ID   ,在docker-compose中通过 network-mode:service:${service-name} 指定
  4. none模式:--net=none

9.2.1 bridge模式

当 Docker server启动时,会自动在主机上创建一个 docker0 虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。

同时,Docker 随机分配一个本地未占用的私有网段(在 RFC1918 中定义)中的一个地址给 docker0 接口。比如典型的 172.17.0.1,掩码为 255.255.0.0。此后启动的容器内的网口也会自动分配一个同一网段(172.17.0.0/16)的地址。

当创建一个 Docker 容器的时候,同时会创建了一对 veth pair 接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即 eth0;另一端在本地并被挂载到 docker0 网桥,名称以 veth 开头(例如 vethAQI2QT)

通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。Docker 就创建了在主机和所有容器之间一个虚拟共享网络。

9.2.2 host模式

一个Docker容器一般会分配一个独立的network namespace。但若启动容器的时候使用host模式,则此容器将不会获得一个独立的network namespace,而是和宿主机共用一个network namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。此时在容器中执行任何类似ifconfig命令查看网络环境时,看到的都是宿主机上的信息;外界访问容器应用时直接使用宿主机地址,不会进行NAT转换。

此外,容器进程跟主机其它 root 进程一样可以打开低范围的端口,可以访问本地网络服务比如 D-bus,还可以让容器做一些影响整个主机系统的事情,比如重启主机。因此使用这个选项的时候要非常小心。

然而,容器除了网络外的其他方面如文件系统、进程等还是和宿主机隔离的。

9.2.3 container模式

此模式下,创建的容器和已存在的一个容器共享一个network namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡、配置自己的IP,而是和一个指定的容器共享IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过lo网卡设备通信。

注意,在这种模式下,假设容器A指定了network-mode为container B的,则处于安全等方面考虑A就不能通过 -p 向外暴露端口了,从而访问不到A上的服务。解决方法:这种模式本质上就是指定了A和B用同一个网络,它们在内部网络共享,故在被依赖者即B指定暴露端口即可。(相关可参阅:https://cizixs.com/2016/06/12/docker-network-modes-explained/)示例:

version: '2'

services:
storage:
image: openzipkin/zipkin-mysql
container_name: zipkin-mysql_sensestudy
ports:
- 9411:9411
restart: always zipkin:
image: openzipkin/zipkin
container_name: zipkin_sensestudy
environment:
- STORAGE_TYPE = mysql
- MYSQL_HOST = localhost
- SCRIBE_ENABLED=true
# - JAVA_OPTS=-Dlogging.level.zipkin=DEBUG -Dlogging.level.zipkin2=DEBUG
# ports:
# Port used for the Zipkin UI and HTTP Api
# - 9411:9411
# Uncomment if you set SCRIBE_ENABLED=true
# - 9410:9410
network_mode: "service:storage"
depends_on:
- storage
restart: always # Adds a cron to process spans since midnight every hour, and all spans each day
# For more details, see https://github.com/openzipkin/docker-zipkin-dependencies
dependencies:
image: openzipkin/zipkin-dependencies
container_name: zipkin-dependencies_sensestudy
entrypoint: crond -f
environment:
- STORAGE_TYPE=mysql
- MYSQL_HOST=localhost
- MYSQL_USER=zipkin
- MYSQL_PASS=zipkin
# Uncomment to see dependency processing logs
# - ZIPKIN_LOG_LEVEL=DEBUG
# Uncomment to adjust memory used by the dependencies job
# - JAVA_OPTS=-verbose:gc -Xms1G -Xmx1G
network_mode: "service:storage"
depends_on:
- storage
restart: always

9.2.4 none模式

此模式下,创建的容器拥有自己的network namespace,但并不会进行任何网络配置。即这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。

10. Docker-Compose

Docker三剑客:

Docker-Machine:管理基础设置:用于在多台机器上快速部署和管理Docker环境(即Docker主机)

Docker-Compose:管理应用:用于组织、编排、管理属于同一个应用的多个容器

Docker-Swarm:管理集群:用于将多个机器上的Docker主机抽象成一个大的虚拟主机加以管理

10.1 是什么

“Compose is a tool for defining and running multi-container Docker applications”。

Docker-Compose是一个用来组织多个容器运行的工具(容器编排?orchestration)。与shell脚本类似,只需要编写一个yml文件就能组织并运行多个容器并对它们进行管理。

关于编排和部署的区别:

编排(orchestration)
编排指根据被部署的对象之间的耦合关系,以及被部署对象对环境的依赖,制定部署流程中各个动作的执行顺序,部署过程所需要的依赖文件和被部署文件的存储位置和获取方式,以及如何验证部署成功。这些信息都会在编排工具中以指定的格式(比如配置文件或特定的代码)来要求运维人员定义并保存起来,从而保证这个流程能够随时在全新的环境中可靠有序地重现出来。

部署(deployment)
部署是指按照编排所指定的内容和流程,在目标机器上执行环境初始化,存放指定的依赖文件,运行指定的部署动作,最终按照编排中的规则来确认部署成功。

类比地看,编排是一个指挥家,他的大脑里存储了整个乐曲此起彼伏的演奏流程,对于每一个小节每一段音乐的演奏方式都了然于胸。而部署就是整个乐队,他们严格按照指挥家的意图用乐器来完成乐谱的执行。最终,两者通过协作就能把每一位演奏者独立的演奏通过组合、重叠、衔接来形成高品位的交响乐。

10.2 使用

使用:编写docker-compose.yml文件即可,可以定义Docker环境变量文件(不指定文件的话默认查找当前目录的.env文件并解析成环境变量),这些变量会传给Docker容器、也可以在yml文件中使用。

示例:

 fversion: "3"
services:
ss_manager: # global unique
image: marchonzsm/sensestudy_manage_server
env_file:
- .env # still default to this even not given
container_name: manage_server_sensestudy_${serverPortOfJava}_${serverPortOfWebsocket}
environment:
POSTGRES_PASSWORD: 1q2w3e4R
SERVICE_8081_NAME: ss_backend_service
SERVICE_8091_NAME: ss_queue_service
SERVICE_8081_TAGS: sensestudy,backend,java
SERVICE_8091_TAGS: sensestudy,backend,websocket
SERVICE_8081_CHECK_TCP: "true"
SERVICE_8091_CHECK_TCP: "true"
# network_mode: "host"
ports:
- ${serverPortOfJava}:8081
- ${serverPortOfWebsocket}:8091
volumes:
- $configFileDirPath/$configFileNameOfService:/home/$configFileNameOfService
- $configFileDirPath/$configFileNameOfDB:/home/$configFileNameOfDB
- "$logDirPath/port_$serverPortOfJava:$logDirPath/port_$serverPortOfJava"
command: --server.port=8081 --sensestudy.websocket.port=8091
restart: unless-stopped

docker-compose.yml

 serverPortOfJava=8085
serverPortOfWebsocket=8095 configFileDirPath=./config_file
configFileNameOfService=application.yml
configFileNameOfDB=application-prod.yml
logDirPath=/sensestudy_logs/javaserver_logs

.env

docker-compose scale可以启多个容器,但若容器需要占用端口则启多个时会端口冲突。可以通过在yml中为不同容器指定多个端口来解决。然而yml的一个不足是key不能含有变量,解决:借助模板,如python的jinja、java的jsp等,Linux下显然前者更方便。这里以jinja为例,将上述yml改造成jinja模板如下:

 version: "3"
services:
{% set numberOfContainerInstance = 3 %}
{% set firstServerPort = 12001 %}
{% for i in range(numberOfContainerInstance) %}
{% set serverPortOfJava = firstServerPort + 2*i %}
{% set serverPortOfWebsocket = firstServerPort + 2*i+1 %}
{% set configFileDirPath = "./config_file" %}
{% set configFileNameOfService = "application.yml" %}
{% set configFileNameOfDB = "application-prod.yml" %}
{% set logDirPath = "/sensestudy_logs/javaserver_logs/port_" ~ serverPortOfJava %}
ss_manager_server_{{ serverPortOfJava }}_{{ serverPortOfWebsocket }}:
image: marchonzsm/sensestudy_manage_server
container_name: manage_server_sensestudy_{{ serverPortOfJava }}_{{ serverPortOfWebsocket }}
environment:
SERVICE_{{ serverPortOfJava }}_NAME: ss_backend_service
SERVICE_{{ serverPortOfWebsocket }}_NAME: ss_queue_service
SERVICE_{{ serverPortOfJava }}_TAGS: sensestudy,backend,java
SERVICE_{{ serverPortOfWebsocket }}_TAGS: sensestudy,backend,websocket
SERVICE_{{ serverPortOfJava }}_CHECK_TCP: "true"
SERVICE_{{ serverPortOfWebsocket }}_CHECK_TCP: "true"
network_mode: "host"
ports:
- {{ serverPortOfJava }}:{{ serverPortOfJava }}
- {{ serverPortOfWebsocket }}:{{ serverPortOfWebsocket }}
volumes:
- {{ configFileDirPath }}/{{ configFileNameOfService }}:/home/{{ configFileNameOfService }}
- {{ configFileDirPath }}/{{ configFileNameOfDB }}:/home/{{ configFileNameOfDB }}
- {{ logDirPath }}:{{ logDirPath }}
command: --server.port={{ serverPortOfJava }} --sensestudy.websocket.port={{ serverPortOfWebsocket }}
restart: unless-stopped
{% endfor %}

docker-compose.yml.jinja

 from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template('docker-compose.yml.jinja')
output_from_parsed_template = template.render()
print output_from_parsed_template # to save the results
with open("docker-compose.yml", "wb") as fh:
fh.write(output_from_parsed_template)

jinja_to_yml.py

10.3 原理

可参阅:https://www.cnblogs.com/sparkdev/p/9787915.html

Docker-Compose中有两个概念:

  1. 服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
  2. 项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。

Docker-Compose 的默认管理对象是项目。

Docker-Compose使用Python编写,实际上调用了Docker服务的API来管理容器。

更多使用方法,详见:

https://docker_practice.gitee.io/compose/install.html

https://blog.csdn.net/pushiqiang/article/details/78682323

遇到的坑:docker-compose.yml中若不指定network-mode则默认为bridge模式,且与docker run不同的是每个compose服务都会创建一个虚拟网桥而不是使用默认的docker0网桥,这样多个网桥分别占用一个地址段,很容易与本机的地址冲突出问题。

解决:显示指定network-bridge: "bridge",这样就不会创建多个bridge而是都使用默认的bridge(docker0)。

11. 进阶_底层实现

(见 底层实现

Docker 底层的核心技术包括 Linux 上的命名空间(Namespaces)、控制组(Control groups)、联合文件系统(Union file systems)和容器格式(Container format)。

命名空间:命名空间是 Linux 内核一个强大的特性。每个容器都有自己单独的命名空间,运行在其中的应用都像是在独立的操作系统中运行一样。命名空间保证了容器之间彼此互不影响。包括pid命名空间、net命名空间、ipc命名空间、mnt命名空间、uts命名空间、user命名空间等。

控制组:是 Linux 内核的一个特性,主要用来对容器的内存、CPU、磁盘IO等共享资源进行隔离、限制、审计等。只有能控制分配到容器的资源,才能避免当多个容器同时运行时的对系统资源的竞争。

联合文件系统:是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。联合文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像;另外,不同 Docker 容器就可以共享一些基础的文件系统层,同时再加上自己独有的改动层,大大提高了存储的效率。Docker 目前支持的联合文件系统(存储驱动)包括 OverlayFSAUFSBtrfsVFSZFS 和 Device Mapper,overlay2 是目前 Docker 默认的存储驱动,以前则是 aufs。

容器格式:最初,Docker 采用了 LXC 中的容器格式。从 0.7 版本以后开始去除 LXC,转而使用自行开发的 libcontainer,从 1.11 开始,则进一步演进为使用 runC 和 containerd。

12. 其他

12.1 重启

docker run通过 --restart 参数来指定重启策略,有 no、on-failure[:max-retries]、unless-stopped、always几种策略,几种策略见名知意。后两个都用于在容器退出时重启容器,不同之处在于Docker Daemon还会在启动时重启restart模式为always的容器。这点很特殊,可以借助之实现应用容器开机自启动。

12.2 Docker日志原理

Docker在运行一个容器时会启动一个goroutine(协程),该goroutine绑定了整个容器内所有进程的标准输出文件描述符,故容器内应用的标准输出会被该routine接收并写入到与该容器一一对应的日志文件中。日志文件位于 /var/lib/docker/containers/<container_id> ,文件名为 <container_id>-json.log。

可通过 docker logs 命令打印输出到容器标准输出的日志信息,其本质就是展示上述日志文件中的内容。

13. 参考资料

《Docker-从入门到实践》

推荐阅读:https://www.cnblogs.com/sparkdev/category/927855.html

最好最权威的资料无疑是 官方文档

进一步阅读:

http://dockone.io/question/512

https://www.cnblogs.com/sparkdev/p/8998546.html Docker生态概览

Docker入门学习总结的更多相关文章

  1. Docker入门学习

    Python爬虫 最近断断续续的写了几篇Python的学习心得,由于有开发经验的同学来说上手还是比较容易,而且Python提供了强大的第三方库,做一个小的示例程序还是比较简单,这不我之前就是针对Pyt ...

  2. docker入门-学习笔记

    docker可以类比成window下的VMware或者virtualbox软件.docker有两个基本的概念:容器(container)和镜像(image),分别对应为VMware中的系统镜像和系统镜 ...

  3. docker 入门学习

    一 : docker 安装(linux-centos7) 安装docker要求 1.docker只支持在64位cup架构计算机上运行,目前不支持32位cup. 2.建议系统的linux内核版本在3.1 ...

  4. [置顶] Docker学习总结(5)——超实用Docker入门学习教程

    Docker是什么 Docker是一种容器技术,它可以将应用和环境等进行打包,形成一个独立的,类似于iOS的APP形式的"应用",这个应用可以直接被分发到任意一个支持Docker的 ...

  5. Docker入门学习笔记

    Docker 什么是Docker 虚拟化技术 在计算机中,虚拟化是一种资源管理技术,将计算机中的各种实体资源如:CPU.硬盘.内存等予以抽象.转换后呈现出来打破实体结构间的不可切割的障碍,使用户可以比 ...

  6. Docker入门学习及其安装

    1.Docker是一个开源的应用容器引擎,基于Go语言并遵从Apache2.0协议开源.Docker可以让开发者打包他们的应用以及依赖包到一个轻量级.可移植的容器中,然后发布到任何流行的Linux机器 ...

  7. Docker 入门实践

    欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~ 作者:张戈 导语 本文从新手视角记录了一个实际的Dokcer应用场景从创建.上传直到部署的详细过程,并简单的介绍了腾讯云容器服务的使用方法 ...

  8. docker入门与部署微服务--学习笔记

    最近公司进一步去windows,走向 linux+云化. 原来的一大坨windows虚拟机服务器都要转向linux, 既然走向linux的话,那么docker肯定是要涉足的. 故学习了docker入门 ...

  9. docker 入门教程(5)——总结与学习资料

    总结 registry:docker镜像仓库,集中存储和管理镜像,类似maven仓库. image:docker镜像,定义容器运行的文件和参数,可以看作是面向对象编程的类. container:doc ...

随机推荐

  1. 51nod 1277 字符串中的最大值

    题目链接 51nod 1277 字符串中的最大值 题解 对于单串,考虑多串的fail树,发现next数组的关系形成树形结构 建出next树,对于每一个前缀,他出现的次数就是他子树的大小 代码 #inc ...

  2. Python图形编程探索系列-05-用控制变量构建对话程序

    跳转到自己的博客 控制变量 变量 符号 意义 默认值 1 var = tk.BooleanVar() 布尔型 0 2 var = tk.StringVar() 字符串控制变量 空字符串 3 var = ...

  3. 用一颗学美术的心来理解PID调节

    用一颗学美术的心来理解PID调节 泡利 3 个月前 相信大家小时候都画过美术作品吧?(什么?你还是宝宝?)没关系,不管你是文科.理科.工科.艺术还是家里蹲的,这篇文章对你来说一定会简单到爆炸的. 这种 ...

  4. 静态代理、JDK动态代理和CGLib动态代理之前的区别

    昨天看了一天的代理方面的知识,刚开始看的时候没看出什么花头来,感觉不实用.一大堆的东西,还不如直接new出来,然后调用方法.后来仔细研究了一下AOP(面向切面)的思想,才发现代理的用处实在太大了.现在 ...

  5. CocosCreator项目结构

    1,通过 Dashboard,我们可以创建一个 Hello World 项目作为开始,创建之后的项目有特定的文件夹结构.[参考来源:官方文档] 2,初次创建并打开一个 Cocos Creator 项目 ...

  6. WTL中最简单的实现窗口拖动的方法(转)

    目前,很多基于对话框的应用程序中对话框都是不带框架的,也就是说对话框没有标题栏.众所周知,窗口的移动都是通过鼠标拖动窗口的标题栏来实现的,那么现在应用程序中的对话框没有了标题栏,用户如何移动对话框呢? ...

  7. UVa 127 - &quot;Accordian&quot; Patience POJ 1214 链表题解

    UVa和POJ都有这道题. 不同的是UVa要求区分单复数,而POJ不要求. 使用STL做会比較简单,这里纯粹使用指针做了,很麻烦的指针操作,一不小心就错. 调试起来还是很费力的 本题理解起来也是挺费力 ...

  8. [Beego模型] 一、ORM 使用方法

    [Beego模型] 一.ORM 使用方法 [Beego模型] 二.CRUD 操作 [Beego模型] 三.高级查询 [Beego模型] 四.使用SQL语句进行查询 [Beego模型] 五.构造查询 [ ...

  9. C# DES (ECB模式) 加密解密 --单倍长

    加密:  调用时: Encrypt_DES16("2AF349243535BCD3", "1111111111111111"); public static s ...

  10. ios webview下纯JS实现长按

    app,其中有长按LI列表弹出菜单,只要清楚五个方法就行:ontouchstart.ontouchmove.ontouchend.setTimeout.clearTimeout 1.首先在我们按下手指 ...