初识Docker

在去年年底的时候,由于刚换电脑,需要重新安装开发环境,觉得十分繁琐,因此查询了解到Docker,当时觉得相关概念晦涩难懂,且本地开发环境也已经搭建完毕,因此没有继续学习Docker相关知识。

近来在开发中遇见一个问题:多个功能工单同时进行测试时,需要在多个开发测试环境之间切换,包括项目分支切换、host修改、nginx配置修改等,应该探索下更方便地开发环境切换方案,加之公司的提测环境也是使用Docker进行搭建,于是决定重新学习Docker相关知识,最终目标是一劳永逸地解决本地开发环境切换的问题。

<!--more-->

参考

1. Docker的用途

刚开始学习的时候,直接啃概念,却发现一脸懵逼。所以在学习一门新技术前,先弄清楚我们为啥要学习它吧。

Docker可以解决虚拟机能够解决的问题,同时也能够解决虚拟机由于资源要求过高而无法解决的问题

跟Docker的图标类似,主角是集装箱而不是船,Docker创造的是软件程序可移植的轻量容器,让其可以运行在任何安装了Docker的机器上,而不用关心底层的操作系统,实现跨平台运行软件,避免重复搭建运行环境。

对于重新搭环境这个事情的麻烦程度,我深有体会。

  • 最开始是租用的虚拟主机托管博客,相关开发环境是集成好的,只需要将php代码通过ftp上传到虚拟机上,这个倒是不是折腾
  • 然后阿里云有活动,19块钱租了一台半年的服务器,开始安装lamp、nginx、node、npm啥的,项目也算是能跑起来
  • 阿里云服务器到期之后,续费的时候一看,一台服务器要300+元/年,加上为了搭ss,于是换成了vultr,重复搭环境~
  • 最近收到了腾讯云的活动,100元/年,入手一台,重复搭环境~

换新工作的时候,也会面临这个问题,IDE、编辑器工具啥的就不提了,只要更换电脑,就得面临重新搭环境的问题(现在是背着自己电脑上下班...)。

在可见的未来,如果某一天需要更换服务器厂商,或是换工作了,搭环境的噩梦就会一直持续。

所以,让我们来学习Docker吧!

2. Docker相关概念

Docker的学习路线比较陡峭,背几个概念先。

2.1. 镜像

Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变

镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。

2.2. 容器

镜像与容器的关系就像是面向对象中的类和实例的关系。镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

如果你要把某个 Image跑起来,那就需要一个 Container。容器的定义并没有提及是否要运行容器,换句话说,容器分为了未运行和正在运行两种状态。

容器是以单进程运行的,被设计用来运行一个应用的,Docker提供了用于分离应用和数据的工具,这导致容器十分轻量,可以便捷地更新代码并重启应用。

使用Docker时必须认识到:容器应该是短暂和一次性的

2.3. 卷

有一点很重要:Container 中所做的改动不会保存到 Image

启动 Image 时可以挂载一个或多个 Volume,Volume 中的数据独立于 Image,重启不会丢失。我们创建一个 Volume,挂载到系统的一个目录下,然后把代码都放进去就可以了。

2.4. 链接

容器启动时将随机分配一个私有IP,其他容器可以使用这个IP与该容器进行通信。这提供了容器之间的通信,且所有容器共享一个本地网络。

2.5. DockerFile

Dockerfile是记录构建镜像步骤的配置文件,借助它可以通过docker build快速创建一个镜像。

Dockerfile包含了一些列指令语法,用于构建镜像。下面整理了常用的指令规则

  • From <Image name>,所有DockerFile都必须以FROM命令开始,用于指定镜像基于哪个基础镜像创建。
  • MAINTAINER <author name>,表示该镜像的维护者
  • RUN <command>,在shell或exec环境下执行命令
  • ADD <src> <destination>,复制文件指令
  • CMD,提供容器的默认执行指令,只允许使用一次CMD指令,多个CMD存在则只有最后一条会执行
  • EXPOSE <port>,指定容器在运行时监听的端口号
  • ENTRYPOINT,配置给容器一个可执行的命令
  • WORKDIR <dirname>,指定RUN、CMD、ENTRYPOINT命令的工作目录
  • ENV <key> <value>,设置环境变量
  • USER <uid>,指定运行时的宿主机账户,默认使用root
  • VOLUME,授权访问从容器内到主机上的目录

在后面的内容中,有使用Dockerfiler构建镜像的例子,现在先大致了解一下对应指令的含义就行了。

3. Docker常用指令

3.1. 查看

这里列举了几个使用频率比较高的指令

docker image ls # 查看安装的镜像
docker rmi <image-id> # 删除对应id的镜像

docker container ls # 查看运行中的容器
docker container ls -a # 查看所有容器
docker container stop <container-id> # 终止一个运行中的容器
docker container start <container-id> # 其中一个被终止的容器

docker rm <container-id> # 删除一个被终止的容器

3.2. 运行容器

docker run <image-id>

docker run 命令先是利用镜像创建了一个容器,然后运行这个容器

docker run = docker create + docker start

docker run 可以携带多个扩展参数,可以使用docker run --help命令查看,下面先整理一些常用的扩展参数

  • --namer container-name,为容器设置别名,后续execdiff等对容器的操作指令都可以通过该别名进行
  • -p base-port:container-port,映射端口号,在本地通过base-port就可以访问到容器中开放的端口号container-port,可以连续使用-p参数映射多个参数
  • -d,使容器以守护状态在后台运行,而不是直接把执行命令的结果输出在当前宿主机
  • -i,让容器的标准输入保持打开。
  • -t,让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上
  • -v base-floder:container-floder,映射本地文件目录到容器的文件系统目录上,这样就可以在容器中访问本地的目录文件了

一般启动容器都会携带-d参数,后续如果要进入容器进行操作,可以使用docker exec -it <container-id>指令进行

3.3. 构建镜像

docker build [OPTIONS] PATH | URL | -

docker build指令通过Dockerfile创建镜像。具体使用可参考文档,常用的参数有下面几种

  • -f <Dockerfile pathname>,用于指定dockerfile,默认当前目录下的Dockerfile文件
  • -t <tag name>,用于指定镜像的名字和标签

有了镜像,我们就可以创建容器,启动应用了~

4. 几个练习Demo

刚开始的时候了解了很多概念,但都不太明白docker到底有什么用,下面用几个例子来理解docker的用法和作用。

4.1. 把容器当做虚拟机处理

docker最底层镜像一般为linux系统,如ubuntu、centos等,因此我们可以把容器当做是虚拟机进行处理。因此使用docker需要我们具备基本的linux虚拟机使用经验。

首先以交互模式启动容器

docker run -it ubuntu:16.04

此时会启动一个终端,我们就可以通过创建的终端来输入命令,这跟我们“通过ssh远程连接了一台ubuntu云服务器然后执行操作”的过程没有啥区别。

下面就可以随便玩玩了。附几个linux教程地址

需要注意的是,Docker是基于应用的,而不是基于系统的。

4.2. 启动nginx服务

在这个例子中,会在docker中运行一个nginx容器,并关联本地端口号到容器,同时将本地机器上的目录映射到容器中的nginx服务器跟目录。

首先,启动一个默认的nginx镜像

docker run --name webserver -d -p 9999:80 nginx

然后,在本地访问localhost:9999就可以看见默认的nginx欢迎界面,第一步算是完成了,然后删除这个容器

docker container ls
docker rm bb3d35fa6984 # 这里的container-id是上面ls查到的

nginx默认根目录位于/usr/share/nginx/html/下,默认文件为index.html,既然要修改nginx默认的欢迎界面,即修改对应的根目录即可。

接下来启动一个关联本地目录到nginx容器的服务器根目录

docker run -d -v /Users/Txm/Desktop/test:/usr/share/nginx/html -p 9999:80 --name web2 nginx

此时在本地访问localhost:9999,看见的就是本地test目录下的index.html文件,这样就达到了我们的目的。

4.3. 使用dockerfile管理一个简单的nodejs服务器

在上面的例子中进行了基本的docker使用练习,而docker的最大作用在于”一次编写,处处运行“,为了实现这个目标,需要借助dockerfile文件。下面这个例子是使用docker来管理一个express服务器。

参考:Docker中运行Node.js web应用

首先编写基本的express服务

mkdir docker-node-hello
cd docker-node-hello

npm init -y
npm i express 
touch index.js

然后使用编辑器编写逻辑代码

var express = require("express");
var PORT = 8080;
var app = express();
app.get("/", function(req, res) {
    res.send("Hello world\n");
});
app.listen(PORT);
console.log("Running on http://localhost:" + PORT);

然后就可以启动express服务器了

node index.js

正常情况下,通过git将代码推送到远程仓库,我们的日常工作一般就到此为止了。如果需要再另外一台机器上运行这个项目,则需要拉取远程仓库的代码,然后执行npm inode index.js等操作,如果是在一台新的机器上运行这个项目,还需要安装node、npm等前置步骤,想想都头大。docker就是用来解决这个问题的!

继续我们的步骤,在docker-node-hello 目录下新建Dockerfile文件,开始编写打包镜像的步骤

# 指定基础镜像,运行在centos系统镜像上
FROM centos:6
# 安装nodejs
RUN     rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
# 安装npm
RUN     yum install -y npm
# 关联当前目录docker-node-hello,到容器的/src目录下
ADD . /src
# 进入容器的/src目录,安装node项目依赖
RUN cd /src; npm install
# 容器对外暴露端口号
EXPOSE  8080
# 定义运行时的node服务和应用入口路径
CMD ["node", "/src/index.js"]

dockerfile包含了构建镜像的相关步骤。接下来执行

docker build -t shymean/docker-node-hello .

然后耐心等待镜像构建完毕(建议在此之前配置好docker的镜像,否则下载会比较缓慢),构建完毕后就可以查看到对应的镜像了

docker images
docker run -p 49160:8080 -d shymean/docker-hello

然后我们在浏览器中输入localhost:49160就可以访问到刚才编写的express应用了。

dockerfile一般会随git版本库进行提交,现在回到前面那个”在新机器上运行这个express项目“的问题,此时,我们只需要保证新机器上安装了docker,然后通过git拉取代码,然后构建镜像,最后根据镜像启动容器就可以了,相关的环境依赖完全由docker解决了。

刚开始对于该流程还存在疑问:上面安装依赖的步骤,貌似通过shell脚本也可以完成,为什么要学习Docker呢?理由就在于:Docker保证了底层运行环境的一致性,如果使用shell,不同的系统,可能需要编写不同的脚本,使用Docker,则完全解决了这个问题。

只要项目能在我们的本地笔记本上运行,就一定能在任何一台安装了docker的机器上运行

当然,这种方式的构建镜像方式还存在一个比较关键的问题:打包后的镜像体积太大了

REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
shymean/docker-hello   latest              94b8e342a74d        3 hours ago         445MB

至于如何定制精简高效的镜像,这将是后面要学习的东西

5. 小结

这篇博客断断续续,写了大概两三周的时间,刚开始整理各种资料,对于docker本身,却有一种无从下手的感觉。

后来尝试了从简单的练习demo入手,学习docker的基本使用方式,理解docker的作用,终于完成了这篇博客~

从最初的虚拟机,到现在已经更换了几家云服务器厂商,每次迁移都需要重新安装开发环境,这实在是让人恼火,现在可以尝试在自己的服务器上使用docker了。

想想我最开始学习docker是要干嘛来着?对了,解决本地开发环境切换的问题。那么接下来,继续学习docker如何应用在测试环境中吧。

在微信小程序中使用webview canvas动画之粒子效果