一, Docker 简介
1.1 什么是 docker
docker 的英文意思是 码头工人, 意思就是搬运东西的意思, 其实这和 docker 的特点是一样的, docker 提供的就是一种容器化搬运东西 (我们的软件, 程序) 的过程. docker 自己本来是运行在操作系统上一个程序软件, 它会提供一个容器环境, 使我们的程序独立地运行在容器中, 所以说, 官方给 docker 起的这个名字也真是应景.
就连图标也是这么生动形象, 富有诗意, 让人浮想联翩....(这是去幼儿园的车, 还没有拐进大学城)
试想下边这样一个场景: 当我们把我们的 web 网站做成分布式的时候, 我们就要加服务器, 然后在各个服务器配置 Web 所需要的配置, 比如: 数据库, Web 服务器, 运行时啥, 这样的我们的网站才能跑起来, 但是每当我们加服务器的时候, 我们都要再重新配置一下, 很繁琐, 有了 docker, 我们就可以把我们的网站和所需要的环境配置好, 打成一个包 (docker 镜像), 然后在服务器上安装 docker, 用 docker 拉取打包好的镜像, 直接 run(容器) 起来就行了, 什么都不用管了, 很方便, 更加的便于管理, 镜像中修改配置, 重新更新, 所有的容器就也能修改了, 我们的网站也就修改更新了, 特别的方便.
1.2 docker 的特点
docker 的特点包含在它的口号中
第一句, 是 "Build, Ship and Run".(搭建, 运输, 运行)
第二句,"Build once,Run anywhere(一次搭建, 到处运行)".
看了上边的场景, 我们可以理解这两句话了, 现在哪个软件不是一次搭建, 到处运行, 真的都不好意思说, 从最开始的 java, 到现在的各种跨平台前端框架(Flutter,ReactNative,Ionic 啥的), 小程序框架(Taro), 这是解放开发人员减少开发成本提高开发效率的三连击造福码农的事情啊, 各种应用层出不穷, 叫我们情何以堪, 不堪回首, 首当其冲, 冲锋陷阵啊, 一不小心就暴露了我的文化.
1.3 docker 和虚拟机
一说到 docker, 相信大家之前也了解过, 那就必须要和虚拟机做一下比较, 其实 docker 是和虚拟机是类似的东西, 我们应该知道虚拟机就是在我们的操作系统上虚拟出来一个电脑, 然后里边可以安装, 运行各种各样的软件, 和我们真的电脑是差不多的, 我们可以拿着这个虚拟好的电脑 (其实是一个文件) 在按了虚拟机的其他电脑上可以直接运行, 里边的东西就不用我们来回安装和配置了, 也是很方便的.
docker 其实提供的也是这么一种的技术, 只不过它比虚拟机效率更加的高, 启动快, 占用资源小等一系列的优点, 而且虚拟机比较笨重, 这是因为虚拟机和 docker 来实现思想上有本质的区别, 我们可以通过下边的两张图可以对比一下:
虚拟机的运作原理: 是虚拟电脑的硬件资源, 把硬件资源分配出来, 然后虚拟出来多个操作系统, 虚拟出来的是一个完整的电脑.
docker 的运作原理: 虚拟的软件资源, 把电脑中的网络, 存储啥的分成几份虚拟成容器, 我们的软件运行在容器中, 每个容易只占用电脑的部分所需要的资源, 并不是一个完整的电脑.
大概就像上边那么理解吧, 反正也不知道是对不对, 但是看起来应该不错.
所以, 从运作原理上来看, docker 更加的轻量级, 虚拟机更加的笨重, docker 启动也十分的快, 部署起来也方便, 所以越来越多的人开始使用 docker 起来.
下边是一张虚拟机和 docker 的对比, 这里要注意一下: 但是 docker 本身并不是容器, 而是创建容器的工具; 而虚拟机它就是虚拟机了.
二, docker 中的核心概念
下面我们来介绍使用 docker 的过程中必须要掌握的概念, 理解这些概念对 docker 的使用和学习是非常必要的.
2.1 镜像(Image)
镜像到底是个什么东西呢, 很多人在学习 docker 的时候都是一头雾水的, 可是是歪果仁对镜像情有独钟吧, 好多东西都有镜像的概念. 比如我们安装系统的. iso 文件, 其实就是镜像, 这里你就可以把镜像认为是一种模板. 我们可以使用 docker 根据这个模板创建容器来运行, 其实更可以理解为镜像是好比 GitHub 上的仓库一样, 我们可以克隆下来源代码然后运行, 运行起来的代码可以是一个网站, 一个应用程序啥的, 这就可以叫做容器. 说白了, 镜像就是一堆静态的模板, 运行起来的镜像就是容器. 镜像一般需要我们拉取下来, 是只读的, 这个我们克隆 GitHub 上的仓库是一样一样的.
docker 镜像中有分层的概念, 就是一个镜像可能基于好几个镜像, 比如一个 Web 运行环境可能需要操作系统 Ubuntu, 数据库 MySQL,.net core runtime 运行时, 那我们拉取的这个镜像就会包好这好几个镜像, 这就好像我们前边说的打包好的运行环境一样, 直接就拉下来一个小电脑一样.
2.2 容器(Container)
当我们拉取了一个镜像, 然后 run 一下, 就会根据这个镜像运行出来一个容器, 运行的容器就好像我们的应用程序一样, 可以访问可以停止, 我们运用多次 run 命令, 就运行了很多很多容器, 也可以说是镜像的实例. 从这个角度来看, 我们可以把镜像看作是类, 容器看作 new 出来的实例, 也是很合适的.
2.3 仓库(Repository)
存放镜像的地方就是仓库, 就好比存放代码的地方是 GitHub 一样, 我们就把 GitHub 称为代码的仓库, GitHub 算是最大的仓库. 那么存放 docker 镜像的地方我们叫做 dockerhub, 是 docker 的中央仓库. 其实已经有 dockerhub 这个网站了( https://hub.docker.com/ ), 这就是 存放 docker 镜像的官方仓库, 好多官方的也保存在这里, 保证了镜像的安全性和可靠性, 我们可以从上边拉取一下镜像来运行我们的软件. 当然我们也可以制作好我们自己镜像推送上去, 不过这些肯定是要官方审核的, 防止有些人写入一些恶意代码. 不过我们可以推到我们自己的 dockerhub 上去, 供我们自己使用, 这个就好我们的 GitHub 账号一样了, 属于私有镜像了.
2.4 数据卷(Volumn)
实际上我们的容器就好像是一个简易版的操作系统, 只不过系统中只安装了我们的程序运行所需要的环境, 前边说到我们的容器是 new 出来的实例, 既然是 new 出来的实例那就会销毁, 那如果销毁了我们的程序产生出的需要持久化的数据怎么办呢, 容器运行的时候我们可以进容器去查看, 容器一旦销毁就什么都没有了. 所以数据卷就是来解决这个问题的, 是用来做数据持久化到我们的宿主机上容器间的数据共享, 简单的说就是将宿主机的目录映射到容器中的目录, 应用程序在容器中的目录读写数据会同步到宿主机上, 这样容器产生的数据就可以持久化了, 比如我们的数据库容器, 就可以把数据存到我们宿主机上的真实磁盘上了.
docker 中基本的概念已经说了, 下面我们就可以来使用 docker 了.
三, docker 的安装与使用
由于 docker 的服务端只能运行在 Linux 操作系统上, 当然我们学习也可以用 Windows 来安装 docker, 但是比较麻烦, 需要开启一系列的配置, 我们还是暂时在一台装有虚拟机的 Linux 系统上来学习 docker. 这里我们以 Linux Ubuntu 16.04 版本来学习.
3.1 docker 的安装
我们先执行命令安装 docker
1. 执行 sudo apt-get update 更新软件包
2. 执行 sudo apt-get -y install docker.io 安装 docker
3. 输入 docker version 检查 docker 是否安装成功
如果有下面的输出, 说明 docker 安装成功
3.2 docker 的使用
docker --help 查看帮助信息, 一般的软件都会有这个命令, 不再多说.
docker version 查看 docker 的版本.
docker info 查看 docker 的基本信息, 有多少个容器, 镜像什么的.
docker images 查看本机上的所有镜像.
REPOSITORY: 仓库, 也是镜像名称.
TAG: 标签, 也是版本号, 镜像会有不同的版本号.
IMAGE ID: 镜像 id, 根据这个 id 我们可以区分不同的镜像, 也可以对某个镜像进行操作.
CREATED: 创建时间.
SIZE: 镜像的大小.
当前我的机器上没有一个镜像, 显示如下:
docker pull <镜像名称>:[标签名称]: 拉取镜像, 默认不写标签名称拉取最新的镜像.
我们输入 docker pull hello-world 拉取最新的 hello-world 镜像
docker run <镜像名称> : 运行一个镜像, 这时候就变成一个容器了, 相当于 new 一个 Image 了. 下边我们 run hello-world 运行这个镜像, 输出以下信息:
至此, 我们的 docker 的 hello-world 已经运行成功了, 接下来我们来启动一个 nginx 服务器, 来从外边访问我们容器里的 nginx 看看.
同样我们先拉取 nginx 镜像 docker pull nginx , 然后等待拉取完成, 运行 nginx 容器, docker run -p 8080:80 nginx
然后我们用自带的浏览器来访问 8080 端口, 出现 welcom to nginx 表示启动成功.
docker 的使用方法基本就是上边这样了, 接下来我们就可以看一下详细的参数.
3.3 docker 的命令
3.3.1 镜像命令:
docker images 查看本机的镜像.
REPOSITORY: 仓库, 也是镜像名称.
TAG: 标签, 也是版本号, 镜像会有不同的版本号.
IMAGE ID: 镜像 id, 根据这个 id 我们可以区分不同的镜像, 也可以对某个镜像进行操作.
CREATED: 创建时间.
SIZE: 镜像的大小.
docker rmi 删除本地的镜像, 加上一个参数 - f 表示强制删除, 因为有时候若有运行的相关容器的时候是不能删除的, 如: docker rmi -f nginx 强行停止容器并删除镜像, 不管是否有占用情况.
docker search 根据镜像名称搜索远程仓库中的镜像, 可以看一下查到所有相关名称的镜像, 可以选择我们要拉取哪个镜像, 下边是搜索 nignx 相关的镜像, 红色部分 ok 说明是官方镜像.
docker pull <镜像名称>:[标签名称]: 拉取镜像, 默认不写标签名称拉取最新的镜像.
docker push 推送镜像, 当我们制作了我们自己的镜像时, 我们就可以推送到我们自己的 docker hub 上去.
3.3.2 容器命令
有了镜像我们就可以 new 一个镜像实例了, 也就是我们所说的容器.
docker run : 基于某个镜像运行一个容器, 如果本地有这个镜像就根据本地的镜像创建, 如果没有, 就去远程拉取一个镜像再创建, 参数如下:
-d: 启动一个容器, 后台运行, 不会占用我们当前的控制台, 一般都要加上, 之前我们启动 nginx 没有指定这个参数, 就会占用当前控制台, 会一直挂起, 有了这个命令就不会占用了.
-i: 以交互模式运行容器, 通常会和 - t 一起来使用(-it).
-t: 为容器也创建一个命令行窗口, 是容器内容的命令行窗口, 比如我们拉取一个 Ubuntu 的镜像, 我们想要在这个操作系统镜像里边执行一些命令, 那就需要这个参数了.
-P: 这个是大写的 P, 指定宿主机的随机端口映射到容器内部的端口.
-p: 这个是小写的 p, 指定某个具体端口映射到容器内部端口, 比如前边我们用 - p 8080:80, 就是让宿主机的 8080 端口映射到容器内的 80 端口, 这样我们就可以在外部用 8080 端口访问我们容器内部的 nginx 了(默认容器必须有一个外部的映射端口, 不然访问不了).
-v: 指定宿主机与容器内部的目录映射, 就是之前的数据卷所需要的参数, 好实现数据的持久化和同步.
--name="mynginx": 为容器指定一个名称, 如果没有指定, 那就分配一个随机名称.
下面用参数重启启动我们的 nginx 镜像: docker run -itd -p 8848:80 --name="mynginx" nginx
docker ps 显示正在运行的容器, 加一个参数 - a 可以看到停止中的容器
docker stop 停止容器 .
docker kill 强制停止容器.
docker restart 重启容器.
docker rm 删除容器, 删除后容器就不在了, 就不能重启和停止了.
docker inspect 查看容器的详细信息.
以上就是差不多我们常用的命令, 具体的其他更多的功能我们可以查看官方文档 https://docs.docker.com/get-started/ .
四, Dockerfile
上边都是我们拉取别人的镜像, 我们实际上也可以制作自己的专属镜像, 根据我们的需要, 配置好我们自己的镜像来使用. Dockerfile 就是用来构建我们的镜像的文件, 在里边可以写一些命令来构建我们的镜像, 构建好后发布到 docker hub 就可以供别人拉取使用了.
Dockerfile 是一个没有后缀名的文本文件, 我们通过写入一些命令来实现镜像的构建. 下面我们先来看看 Dockerfile 是怎样来编写的. 下边的命令标识形式<> 代表需要的参数,[ ] 是可选参数.
FROM: 指定基础镜像, 所有构建的镜像都必须有一个基础镜像, 且 FROM 命令必须是 Dockerfile 的第一个命令.
FROM <image> [AS <name>] 指定从一个镜像构建 AS 起一个新的镜像名字.
FROM <image>[:<tag>] [AS <name>] 指定镜像的版本 tag.
例如: FROM MySQL:5.0 AS database
MAINTAINER: 镜像维护人的信息.
MAINTAINER <name>
例如: MAINTAINER haha lsdjfl@163.com
RUN: 构建镜像时要执行的命令.
RUN <command>
例如: RUN ["executable", "param1", "param2"] RUN ["dotnet restore","*.csproj"]
ADD: 将本地的文件添加复制到容器中去, 压缩包会解压, 可以访问网络上的文件, 会自动下载.
ADD <src> <dest>
例如: ADD *.csproj /App 添加 csproj 文件到容器中的 App 目录下.
COPY: 功能和 ADD 一样, 但是只是复制, 不会解压或者下载文件.
CMD: 启动容器后执行的命令, 和 RUN 不一样, RUN 是在构建镜像是要运行的命令. 当使用 docker run 运行容器的时候, 这个可以在命令行被覆盖.
CMD ["executable", "param1", "param2"]
ENTRYPOINT: 也是执行命令, 和 CMD 一样, 只是这个命令不会被命令行覆盖.
ENTRYPOINT ["executable", "param1", "param2"]
例如: ENTRYPOINT ["donnet", "myapp.dll"]
LABEL: 为镜像添加元数据, key-value 形式的.
LABEL <key>=<value> <key>=<value> <key>=<value> ....
例如: LABEL version="1.0" description="这是一个 web 应用"
ENV: 设置环境变量, 有些容器运行时会需要某些环境变量 比如: JAVA_HOME.
ENV <key> <value> 一次设置一个环境变量.
ENV <key>=<value> <key>=<value> <key>=<value> .... 设置多个环境变量.
例如: ENV JAVA_HOME /usr/java1.8/
EXPOSE: 暴露对外的端口.
EXPOSE <port>
例如: EXPOSE 80
这里指的是容器内部程序的端口, 虽然会和宿主机的一样, 但是其实是两个端口, 容器运行时, 需要用 - p 映射外部端口才能访问到容器内的端口.
VOLUME: 指定数据持久化的目录, 官方语言叫做挂载.
VOLUME /var/log 指定容器中需要被挂载的目录, 会把这个目录映射到宿主机的一个随机目录上, 实现数据的持久化和同步.
VOLUME ["/var/log","/var/test".....] 指定容器中多个需要被挂载的目录, 会把这些目录映射到宿主机的多个随机目录上, 实现数据的持久化和同步.
VOLUME /var/data var/log 指定容器中的 var/log 目录挂载到宿主机上的 / var/data 目录, 这种形式可以手动指定宿主机上的目录.
WORKDIR: 设置工作目录, 设置完工作目录之后 , 上边的 RUN,CMD,COPY,ADD 等的工作目录都变成这个了.
WORKDIR <path>
例如: WORKDIR /App/test
USER: 指定运行命令时所使用的用户, 为了安全和权限起见, 有的用户可能权限高, 有的用户可能权限低, 根据要执行的命令选择不同的用户.
USER <user>:[<group>]
例如: USER test
ARG: 设置构建镜像是要传递的参数.
ARG <name>[=<value>]
例如: ARG name=sss
以上是差不多构建 Dockerfile 时所使用的命令的, 构建时命令是从上到下顺序执行的, 可能后边的命令需要前边命令的结果, 使用 docker build 编译 Dockerfile 就可以构建我们的镜像了.
docker build 可以使用参数 -f 指定 Dockerfile 的目录, 默认是在当前目录下找;-t 指定构建的镜像的名称和标签, 例如: docker build -f ./aa/bb -t myimage:1.0
五, 构建我们自己的镜像
有了上面的基础, 我们就可以构建我们自己的镜像了, 然后还可以上传到我们自己的 docker hub 上供别人拉取使用. 接下里我们就来实现以下. 这里参考了官方的构建步骤: https://docs.docker.com/engine/examples/dotnetcore/
5.1 创建一个 mvc 项目
首先找一个目录, 创建一个文件夹 DockerDemo, 然后在该目录下执行命令 dotnet new mvc, 创建我们的 mvc 程序, 如下:
5.2 添加 Dockerfile 文件
我们直接添加一个名称为 Dockerfile 的文件, 然后输入一下命令:
- # 构建 sdk 镜像, 是为了进行编译, 发布我们的 Web 应用
- FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build-env
- # 在容器中设置一个 / App 目录
- WORKDIR /App
- # 复制 csproj 文件 到当前目录下(App 目录下) 并执行 dotnet restore 还原包
- COPY *.csproj ./
- RUN dotnet restore
- # 再复制其他剩余的文件到当前目录, 执行发布命令, 用 Release 模式 并且输出到 out 文件夹
- COPY . ./
- RUN dotnet publish -c Release -o out
- # 构建运行时镜像
- FROM mcr.microsoft.com/dotnet/core/aspnet:2.2
- # 设置运行时镜像一个 / App 目录
- WORKDIR /App
- # 复制之前的镜像的产物中的 /App/out 目录下的文件 到当前新镜像的 /App 录下(当前目录)
- COPY --from=build-env /App/out .
- # 执行 dotnet 命令, 运行我们的 Web 应用
- ENTRYPOINT ["dotnet", "DockerDemo.dll"]
我用 vscode 添加 Dockerfile 直接变成了一个鲸鱼的图标, 说明是一个 Docker 文件, 还有高亮:
5.3 构建 Docker 镜像
我们直接把 DockerDemo 这个文件夹直接复制到虚拟机中, 我复制到了 Desktop 下:
然后进入到 DockerDemo 目录下, 直接执行命令 docker build -t dockerdemo . , 这句命令的意思是 使用当前目录下的 Dockerfile 构建镜像 dockerdemo, 注意构建的镜像名称必须为小写, 否则会报错.
然后就是等待构建了, 可能会比较慢, 如果想快的话, 可以换成国内的镜像源, 这里就不说了, 因为我也懒得换.
这次构建过程会分为两步, 因为有两个镜像, 构建完成后, 我们使用 docker images 查看, 会发现有我们新构建的镜像 dockerdemo:
5.4 运行容器
接下来我们输入命令 docker run -d -p 8848:80 --name myapp dockerdemo, 运行一个容器, 名称叫做 myapp
使用 docker ps 查看运行中的容器:
5.5 访问我们的 mvc 程序
在虚拟机的浏览器中输入 http://localhost:8848, 或者在你的电脑上输入虚拟机的地址来访问 http://192.168.226.130:8848/ (这个是我的地址) , 显示如下, 表名成功:
六, 发布镜像
我们已经构建好了我们的镜像, 接下来我们就把镜像推送到我们自己的 docker 仓库中去, 这一步和 GitHub 是完全一样的, 就像把我们的代码推送到 GitHub 上一样.
6.1 创建账号
首先, 我们需要创建一个 dockerhub 账号, 登录 https://hub.docker.com/ , 根据提示注册账号, 这一步就不细说了.
6.2 登录
命令行输入 docker login, 输入我们的账号密码, 进行登录.
6.3 推送镜像
推送镜像之前, 首先要标记我们的镜像, 也就是给我们的镜像起一个名字, 使用命令 docker tag <image> <username>/<repository>:<tag>, 下边我们就标记我们刚才的 dockerdemo 的镜像:
使用命令 docker tag dockerdemo emmaccc/dockerdemo:1.0.0
查看我们刚才标记过的镜像:
使用命令 docker push emmaccc/dockerdemo:1.0.0 推送镜像, 等待一段时间后推送成功, 就可以在自己 dockerhub 账号上看见了
6.4 使用镜像
推送成功之后, 我们就可以在任意一台安装了 docker 的计算机上来拉取我们镜像, 直接 run 启动我们的 Web 应用了.
docker run -p 4396:80 emmaccc/dockerdemo:1.0.0, 运行之后, 无需担心各种依赖, 配置, 也不用在你的新主机上安装任何东西, 直接 run 运行就行了, 这也 docker 独特的魅力所在.
六, 总结
学习 docker, 前前后后看了好几遍, 究竟是个什么玩意啊, 好半天才看懂, docker 的基本使用就差不多了, 接下来估计又是 k8s 了, 唉, 东西真多, 感觉全 it 界都在造轮子吧, 非要搞这个搞那个, 没办法, 还是得跟着大佬们走, 如果你真的还不明白, 那就送你一句话.
Web 放 tomcat 里, tomcat 放 docker 里, docker 放 k8s 里, k8s 放操作系统上, 绕了一大圈, 扯淡呢, 咋不直接 Web 放操作系统上直接运行呢.
来源: http://www.tuicool.com/articles/6Jne6jA