不断更新中…
tips: 本文大部分是提取自官方文档的翻译+stackoverflow上的解释,也有自己查找的其它内容,所以厚颜无耻地标记为原创
我想要坚持一个简单的原则:
不能让简单的事情复杂化,也不能让复杂的事情简单化,而是列出关键点让大家知道为什么复杂的事情会那么复杂
1 Lightweight(轻量) : 一个能运行几个VM的主机,可以运行上千个容器
2 Portable(便携): 本地构建好image(镜像), 部署到任何地方
3 Loosely coupied(松耦合): 容器需要的运行环境是highly self sufficient的, 荣期间互相隔离没有冲突
1 具有可伸缩性(容器会更好一些)
2 安全性
每个容器与它们自己私有的文件系统(由image提供)交互,里面包含了所有运行该应用需要的东西: 代码或binary程序,运行时环境,依赖,需要的文件系统
如下图,Docker层直接与容器和主机操作系统交互,不需要hypervisor层也不需要客户机操作系统。一个在Linux中运行的docker会与主机操作系统共享内核
首先我们需要知道,这是一个 Client-Server 架构。我们需要理解其中的重要概念:
1 Docker daemon(后台) (dockerd) ,一个使用dockerd命令一直运行着的客户端程序,监听REST API与客户端交互,处理客户端请求,管理着众多Docker对象
2 Docker client , 与用户交互的客户端
3 Docker registries(仓库), 存放Docker image的地方。比如Docker Hub就是默认的公共仓库。我们也可以构建自己的私有Docker镜像仓库。
4 Images(镜像)
一个只读的模板。每一个Docker容器运行都需要image, 通常,一个镜像是基于其它镜像建立的。比如,在ubuntu镜像里安装nginx服务器并配置好,现在就可以得到一个ubuntu+nginx的镜像。如果需要自己构建镜像,需要使用Dockerfile,这是一个使用简单的语法告诉docker如何创建,运行镜像的包含一系列指令的程序文件。
5 Containers(容器)
一个容器是运行image的实例,与其它容器还有客户机操作系统都是隔离开的。我们可以管理一个容器如何隔离它自己的网络,存储方式。
当我们输入如下命令:
docker run -i -t ubuntu /bin/bash
1 如果本地没有 ubuntu 镜像, Docker 将自动从仓库中pull一个ubuntu镜像下来,如同我们自己执行了 docker pull ubuntu
2 Docker 会创建一个新的容器,如同我们自己执行了 docker container create
3 Docker 在容器里创建一个可读/写的文件系统,作为此容器的final layer(最底层)
4 因为我们自己没有指定此docker容器的网络配置,它会自己创建网络接口以连接到其它容器与默认网络。每个Docker容器是有自己的IP地址的 , 默认情况下容器可以连接本地操作系统所连接到的网络。
5 Docker开始运行这个容器,因为命令中指定了 -i 与 -t ,我们可以使用当前终端与docker客户端进行输入输出交互。
6 当我们在终端中输入 exit 终止/bin/bash,这个容器就停止了,但它不会被删除,我们仍可以重新启动这个容器。
1 Namespaces (命名空间): Docker使用namespaces技术来实现容器间的隔离,每当创建一个容器,Docker就会为它创建一个namespace集。我们列出几个Linux下Docker引擎使用的namespace:
2 Control groups: Linux上运行的Docker引擎使用control groups (cgroups, 可以限制一个应用的可访问资源)这种技术,可以让Docker在共享操作系统硬件资源给容器的同时对特定容器访问这部分资源加以限制。
3 Union file systems; UnionFS(通用文件系统),非常轻量,运行速度快。Docker引擎使用UnionFS块供给各个容器。
4 Container format: Docker引擎将namespaces, control groups, UnionFS 打包,称作Container format,默认的包是libcontainer.
如果使用Window, 去这里下载: Docker Desktop for Windows
如果使用Mac, 去这里下载: Docker Desktop for Mac
如果使用Linux:
检查安装是否成功(linux下需加sudo):
docker info,查看安装docker信息
docker run hello-world,如果看到Hello from Docker! 则安装成功
docker image ls, 可以看到安装的image
docker container ls --all, 可以看到存在的容器
首先要知道,可以通过一个image运行多个容器,就比如一个程序可以开多个终端运行
准备好自己的要部署的应用的代码(里面还要有配置文件,比如maven用pom.xml或者npm直接用xxx.json,里面写好这个项目所依赖的库文件)
这里我们使用别人做好的一个子弹公告牌项目(很简单的一个运行在node.js上的小网站,发布公告信息用的)样例代码,模拟我们自己的项目使用docker部署的过程:
首先, 从github上下载项目代码
如果使用的是Linux系统,直接运行
git clone https://github.com/dockersamples/node-bulletin-board
进入node-bulletin-board/bulletin-board-app 目录下
如果使用的是Linux系统,直接运行
cd dockersamples/node-bulletin-board
我们可以看到当前目录就是存放代码文件和配置文件的"根路径"啦
不过这里,根目录下已经有一个官方做好的Dockerfile了,打开看看:
# 一般Dockerfile都从FROM开始,即先从以有的镜像开始
# 从Docker的默认仓库中下载一个叫node的镜像(就是node.js,镜像链接在https://hub.docker.com/_/node)
# ":-current-slim" 是指定这个镜像的版本号,可以去上面链接看看,这个image分很多版本的,而current-slim指定当前最新的版本
# 同理,如果想安装Python,可以指定From python:3.6
FROM node:current-slim
# WORKDIR指定这个镜像文件系统中的工作目录
# 镜像里是有自己的文件系统的,设置此应用在镜像中的工作目录为 /usr/src/app
WORKDIR /usr/src/app
# COPY 命令,可以从本地操作系统复制文件到镜像文件系统中
# 复制你的本地操作系统当前路径下的文件, 一个叫package.json的,这是node.js需要的后续安装各种库需要的配置文件,把它从本地复制到
# 镜像文件中的当前目录("."指代当前目录,也就是我们刚才指定的"/usr/src/app"下)
COPY package.json .
# RUN 命令,在镜像文件系统中运行命令
# 这里是执行"npm install"命令,npm是Node.js的包管理器,它自动寻找当前目录(镜像文件系统中的/usr/src/app)下的json文件,即package.json,然后安装文件中的软件包
RUN npm install
# 容器默认是和外界操作系统隔离的,从文件系统到网络都是如此,那我们怎么使用这个容器呢? 很简单,用ip+端口的方式就行
# 先用EXPOSE命令,后面指定该容器暴露的端口号(它就通过这个端口和外界交互数据)
EXPOSE 8080
# 在容器里运行命令怎么办呢, 使用CMD,一条命令(npm start)的单词间本来使用空格分开,这里使用字符串隔开的方式传入中括号
# CMD会代我们在容器中指定这个命令: npm start, 指在容器中启动node.js服务器
CMD [ "npm", "start" ]
# 又是COPY命令,第一个点指的是本地操作系统环境的当前目录 第二个点指的是容器文件系统里的当前目录(/usr/src/app)
# 即把本地当前目录下的所有文件(现在正是迁移代码的一步), 复制到容器里的工作目录下, node.js看到有文件了就能进行构建网站了
COPY . .
{ 注意
RUN和CMD的区别:
RUN命令只存在于Dockerfile中,即制作image过程中,每次执行一个RUN命令,都会得到一个新的image(同时移除刚才的image)。一个Dockerfile可以有多个RUN命令
CMD是指运行这个image容器那个时刻执行的命令,一个Dockerfile只能有一个CMD命令。如果运行容器时不想用默认的启动命令,可以用:
docker run <镜像名/号> <其它命令>
来覆盖这个镜像原有的启动时触发的在镜像中执行的命令。
}
在终端中使用docker image build(linux下要加sudo)这个Dockerfile制作我们自己的镜像:
这里 -t 代表标记,即每个image都会有一个tag(标记)作为版本号,这里说明image名为bulletinboard, 标记为1.0
最后的点代表当前目录,即在当前目录下运行Dockerfile
docker image build -t bulletinboard:1.0 .
可以看到,输出会依次执行Dockerfile中每个step,每当执行一个步骤,会----> image ID号,代表不断删除前一个镜像,得到新的镜像并分配新的镜像ID。最后当我们看到
Successfully built 2e0fe5b3fe75
Successfully tagged bulletinboard:1.0
这两行,代表制作镜像成功,Docker还会为我们制作的image分配一个ID, 这里可以看到我的ID是2e0fe5b3fe75
想查看自己的本地现在都有哪些镜像吗?
在终端执行以下命令:
docker image ls
使用docker container run命令:
docker container run --publish 8000:8080 --detach --name bb bulletinboard:1.0
详解:
现在我们可以尝试用浏览器访问 http://localhost:8000 这个链接
当你看到如下页面,代表这个容器已经成功启动:
此时,这个网站应用在docker容器中运行,效果却像我们在本地运行了这个应用一样
如果你忘记容器名或ID,先查看当前正在运行的容器列表:
docker container ls
执行以下命令:
docker stop <容器名/容器ID>
再次访问 http://locahost:8000, 网页就无法访问了,代表容器成功停止
因为容器被停止了,所以我们无法通过docker container ls命令查看容器,但是可以这样做:
docker container ls -a
代表列出所有的本地容器,运行的和没运行的容器都会被列出
现在我们运行容器,不需要指定新的端口,image名,容器名了,直接:
docker container start <容器名/容器ID>
即可运行容器,它的运行方式还是遵循一开始的容器运行方式(端口映射之类)
访问 http://localhost:8000 , 将看到网站又启动了
如果想删除某容器(并不会删除容器所使用的image),使用如下命令:
docker container rm --force <容器名/容器ID>
–force 代表如果这个容器正在运行,就把它停掉再删除
这里我们把这个image分享到Docker Hub上,步骤和Github很像。
如果没有DockerHub帐号,先去这里注册一个Docker Hub帐号,其中Docker ID即为你的昵称。注册成功之后,登录你的帐号。
点击页面里上方的Repository(仓库) -> 点击Create(创建),并选择private(私有)
(如果创建的是public(公共)仓库,可以创建仓库后点击 Settings -> 点击 Make private)
输入自己的repository名(一定要和本地镜像名一样),比如我当前会输入bulletinboard
1 先在本地命令行中登录:
docker login
根据提示依次输入自己在Docker Hub中的Docker ID和密码
2 标记
命令格式如下:
docker image tag <要上传的本地镜像名A>:<这个本地镜像的版本号> <你的Docker ID>/<远程镜像名B>:<版本号>
A与B应该完全相同(这个在DockerHub创建仓库时就应该保证)
比如我的Docker ID是nilezhou, 所以:
docker image tag bulletinboard:1.0 nilezhou/bulletinboard:1.0
3 上传
命令格式如下:
sudo docker push <上一个命令的最后一个字段>
比如我用的是:
sudo docker push nilezhou/bulletinboard:1.0
等待上传即可,当看到终端输出sha256(可用于文件完整性校验,防文件被篡改或缺失用)时,代表上传成功
再打开DockerHub看,就能看到自己上传的镜像了
我们上传的只是镜像而已,那么这个镜像里包含什么,Dockerfile如何管理呢?
我们可以编辑在DockerHub上创建的Repository中的Readme,描述这个镜像包含什么东西,有什么值得注意的。
至于Dockerfile,建议就和应用的源代码放在同一个文件夹下,然后使用Git版本控制(比如将代码上传到github)进行管理。
关于Dockerfile的详细文档 详细文档:
如果您想开发基于Docker容器的应用,而非单纯用Docker进行运维,请继续往下看:
个人就比较讨厌大几百M甚至上G的image, 我还不如自己动手写几个shell脚本呢,有必要用docker吗?
1 选择适合的基本image, 比如如果你只需要执行Java的环境,那么下载一个openjdk镜像就够了,而不是先下一个ubuntu镜像再在里面安装openjdk
2 使用multistage builds(多阶段构建), 比如: