Docker,是一款现在最流行的 软件容器平台,提供了软件运行时所依赖的环境。
物理机
硬件环境,真实的 计算机实体,包含了例如物理内存,硬盘等等硬件;
虚拟机:
在物理机上 模拟出一套硬件环境和操作系统,应用软件可以运行于其中,并且毫无感知,是一套隔离的完整环境。本质上,它只是物理机上的一份运行文件。
为什么需要虚拟机?
环境配置与迁移
在软件开发和运行中,环境依赖一直是一个很头疼的难题,比如你想运行 node 应用,那至少环境得安装 node 吧,而且不同版本,不同系统都会影响运行。解决的办法就是我们的包装包中直接包含运行环境的安装,让同一份环境可以快速复制到任意一台物理机上。
资源利用率与隔离
通过硬件模拟,并包含一套完整的操作系统,应用可以独立运行在虚拟机中,与外界隔离。并且可以在同一台物理机上,开启多个不同的虚拟机启动服务,即一台服务器,提供多套服务,且资源完全相互隔离,互不影响。不仅能更好提高资源利用率率,降低成本,而且也有利于服务的稳定性。
传统虚拟机的缺点
资源占用大
由于虚拟机是模拟出一套完整系统,包含众多系统级别的文件和库,运行也需要占用一部分资源,单单启动一个空的虚拟机,可能就要占用 100+MB 的内存了。
启动缓慢
同样是由于完整系统,在启动过程中就需要运行各种系统应用和步骤,也就是跟我们平时启动电脑一样的耗时。
冗余步骤多
系统有许多内置的系统操作,例如用户登录,系统检查等等,有些场景其实我们要的只是一个隔离的环境,其实也就是说,虚拟机对部分需求痛点来说,其实是有点过重的。
Linux 容器
Linux 中的一项虚拟化技术,称为 Linux 容器技术(LXC)。它在进程层面模拟出一套隔离的环境配置,但并没有模拟硬件和完整的操作系统。因此它完全规避了传统虚拟机的缺点,在启动速度,资源利用上远远优于虚拟机;
Docker
Docker 就是基于 Linux 容器的一种上层封装,提供了更为简单易用的 API 用于操作 Docker,属于一种容器解决方案。
基本概念: 在 Docker 中,有三个核心的概念:
镜像 (Image):
- 从原理上说,镜像属于一种root 文件系统,包含了一些系统文件和环境配置等,可以将其理解成一套最小操作系统。为了让镜像轻量化和可移植,Docker 采用了Union FS 的分层存储模式。将文件系统分成一层一层的结构,逐步从底层往上层构建,每层文件都可以进行继承和定制。这里从前端的角度来理解:镜像就类似于代码中的 class,可以通过继承与上层封装进行复用。
- 从外层系统看来,一个镜像就是一个 Image二进制文件,可以任意迁移,删除,添加;镜像是容器的基石,不包含任何动态数据。
容器 (Container):
- 镜像是一份静态文件系统,无法进行运行时操作,就如class,如果我们不进行实例化时,便无法进行操作和使用。因此容器可以理解成镜像的实例(镜像运行的实体),即new 镜像(),这样我们便可以创建、修改、操作容器;一旦创建后,就可以简单理解成一个轻量级的操作系统,可以在内部进行各种操作,例如运行 node 应用,拉取 git 等;
- 基于镜像的分层结构,容器是以镜像为基础底层,在上面封装了一层容器的存储层;
- 存储空间的生命周期与容器一致;
- 该层存储层会随着容器的销毁而销毁;
- 尽量避免往容器层写入数据;
- 容器中的数据的持久化管理主要由两种方式:
- 数据卷 (Volume): 一种可以在多个容器间共享的特殊目录,其处于容器外层,并不会随着容器销毁而删除;
- 挂载主机目录: 直接将一个主机目录挂载到容器中进行写入;
- 容器运行着真正的应用进程。有着初建、运行、停止、暂停和删除5种状态。本质上是在主机上运行的一个进程,但容器有自己独立的命名空间隔离和资源 限制。
仓库 (Repository):
- 为了便于镜像的使用,Docker 提供了类似于 git 的仓库机制,在仓库中包含着各种各样版本的镜像。官方服务是 Docker Hub;
- 可以快速地从仓库中拉取各种类型的镜像,也可以基于某些镜像进行自定义,甚至发布到仓库供社区使用;
安装
- linuxcurl -sSL https://get.docker.com/ | sh或者wget -qO- https://get.docker.com/ | sh 
| 1 | curl https://get.docker.com/ > install-docker.sh # 下载安装脚本 | 
第一个 docker 项目
接下来我们搭建一个能够托管静态文件的 Nginx 服务器,容器运行程序,而容器哪来的呢?容器是镜像创建出来的。那镜像又是哪来的呢?
镜像是通过一个 Dockerfile 打包来的,它非常像我们前端的package.json文件。
| 1 | Dockerfile: 类似于“package.json” | 
创建文件
| 1 | hello-docker | 
- index.html - 1 - <h1>Hello docker</h1> 
- Dockerfile - 1 
 2
 3
 4
 5- FROM nginx 
 COPY ./index.html /usr/share/nginx/html/index.html
 EXPOSE 80
打包镜像
| 1 | cd hello-docker/ # 进入刚刚的目录 | 
此时遇到问题Docker 安装后 报 Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
| 1 | systemctl daemon-reload | 
然后重新运行 docker image build ./ -t hello-docker:1.0.0 命令。这条命令的意思是基于路径./(当前路径)打包一个镜像,镜像的名字是hello-docker,版本号是1.0.0。该命令会自动寻找
Dockerfile来打包出一个镜像。
解读下Dockerfile的内容:
- FROM nginx:基于哪个镜像
- COPY ./index.html /usr/share/nginx/html/index.html:将宿主机中的./index.html文件复制进容器里的/usr/share/nginx/html/index.html
- EXPOSE 80:容器对外暴露80端口
注意!Docker 中的选项(Options)放的位置非常有讲究,docker –help image和docker image –help是完全不同的命令
配置国内镜像源
在 Linux 环境下,我们可以通过修改 /etc/docker/daemon.json ( 如果文件不存在,你可以直接创建它 ) 这个 Docker 服务的配置文件达到效果。
- Docker中国官方镜像加速1 
 2
 3
 4
 5{ 
 "registry-mirrors": [
 "https://registry.docker-cn.com"
 ]
 }
- 网易163镜像加速1 
 2
 3{ 
 "registry-mirrors": ["http://hub-mirror.c.163.com"]
 }
- 中科大镜像加速1 
 2
 3{ 
 "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
 }
- 直接下载站点镜像docker pull hub.c.163.com/library/tomcat:latest //复制站点链接用 pull 下来 
然后重启 docker 让配置生效
1
sudo systemctl restart docker
通过 docker info 来查阅当前注册的镜像源列表,验证我们配置的镜像源是否生效
1
sudo docker info
运行容器
| 1 | docker container create -p 9000:80 hello-docker:1.0.0 # 根据镜像创建容器 | 
然后在浏览器打开ip:9000,就能刚刚自己写的index.html内容。
在上边第一个命令中,我们使用docker container create来创建基于hello-docker:1.0.0镜像的一个容器,使用-p来指定端口绑定——将容器中的80端口绑定在宿主机的9000端口。执行完该命令,会返回一个容器ID 而第二个命令,则是启动这个容器启动后,就能通过访问本机的9000端口来达到访问容器内80端口的效果了
Tips: 你可以使用docker container ls来查看当前运行的容器 当容器运行后,可以通过如下命令进入容器内部:
原理实际上是启动了容器内的/bin/bash,此时你就可以通过bash shell与容器内交互了。就像远程连接了SSH一样
退出容器
| 1 | exit # 直接退出 | 
制作镜像
| 1 | docker commit -m='message' -a='cosyer' containerId hello-docker:1.0.1 | 
DockerFile
- FROM:这个镜像依赖谁(基础镜像)
- MAINTAINER:这个镜像是谁写的(维护者的信息)
- RUN:构建镜像需要运行的命令
- ADD:步骤:copu 文件,会自动解压
- WORKDIR:设置当前工作目录
- VOLUME:设置券,挂载主机目录
- EXPOSE: 暴露端口
- CMD:指定这个容器启动的时候要运行的命令 只有最后一个会生效,可被替代
- ENTRYPOINT:指定这个容器启动的时候要运行的命令,可以追加命令
- ONBUILD:当构建一个被继承 DockerFile 这个时候就会运行 ONBUILD 的命令,触发指令
- COPY:类似 ADD 将我们的文件拷贝到镜像中
- ENV:构建镜像的时候设置环境变量
发布镜像
- docker login
- docker tag imageId message
- docker push imageId
重命名镜像
| 1 | docker tag busybox:latest mybusybox:latest | 
总结
- 写一个 Dockerfile
- 使用docker image build来将Dockerfile打包成镜像
- 使用docker container create来根据镜像创建一个容器
- 使用docker container start来启动一个创建好的容器
| 1 | # images | 

| 1 | iptables --list | grep DOCKER | 
- 遇到了centos7系统docker 宿主机不能访问容器问题,unbantu系统没问题1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16#停止docker 
 systemctl stop docker
 #关闭docker0
 ip link set dev docker0 down
 #删除docker0网桥
 brctl delbr docker0
 #防火墙设置,后来发现这一步不用执行可以
 iptables -t nat -F POSTROUTING
 #增加docker0 网桥
 brctl addbr docker0
 #增加网卡
 ip addr add 172.16.10.1/24 dev docker0
 #启用网卡
 ip link set dev docker0 up
 #重启docker服务
 systemctl restart docker
三种网络模式
- bridge(桥接)
- host(主机模式与宿主机共享网络)
- none(不配置网络)1 
 2
 3
 4
 5
 6
 7# 自定义网络 
 docker network create --driver bridge --subnet 192.168.3.0/16 --gateway 192.168.3.1 mynet
 # 查看网络列表
 docker network ls
 # 删除网络
 docker neteork rm id
SPA应用静态站点迁移
之前的步骤:
- 本地npm run build打包静态文件
- 手动FTP传输到服务器
- git push更新github
自动化CI:
- 执行git push
- 自动检测到 github 有代码更新,自动打包出一个 Docker 镜像
- CI 编译完成后,SSH 登录 VPS,删掉现有容器,用新镜像创建一个新容器
好处:
- 不必再手动 FTP 上传文件
- 当进行修改错别字这样的简单操作时,可以免测。改完直接git push,而不必本地npm run build
travis
免费的CI资源travis,需要在/hub.docker.com注册账号。 使用 Github 登录 Travis CI 后,在左边点击+加号添加自己的 Github 仓库后,需要移步到 Setting 为项目添加DOCKER_USERNAME和DOCKER_PASSWORD环境变量。这样保证我们可以秘密的登录 Docker Hub 而 不被其他人看到自己的密码。
| 1 | language: node_js | 
然后需要添加 Dockerfile 文件来描述如何打包 Docker 镜像。 按照.travis.yml的命令次序,在打包镜像时,npm run build已经执行过了,项目产出已经有了。不必在 Docker 容器中运行npm install和npm run build之类的,直接复制文件即可:
| 1 | FROM nginx | 
将不访问文件的请求全部重定向到/index.html,处理history路由vhost.nginx.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19server {
    listen 80;
    server_name localhost;
    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
        proxy_set_header Host $host;
        if (!-f $request_filename) {
          rewrite ^.*$ /index.html break;
        }
    }
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }
}
nginx反向代理
宿主机/etc/nginx/conf.d/vhost.conf
1
2
3
4
5
6
7
8server {
    listen 80;
    server_name pea3nut.info;
    location / {
        proxy_pass http://127.0.0.1:8082;
    }
}
nodejs站点迁移(express)
之前的步骤:
- 本地修改好 Ejs 或者其他文件
- 手动通过 FTP 上传到服务器
- 在服务器端重启 Nodejs 进程。若有 npm 包依赖改动,需要在VP S服务器上手动执行npm install
- git push更新 Github 源码
Tips: 你可能发现了 Dockerfile 中的ENTRYPOINT命令必须指定一个前台进程。若你的 Nodejs 应用是使用 PM2 进行保护的,你需要替换pm2 start app.js为pm2-docker app.js
docker-compose
docker-compose 是 Docker 官方提供的一个 Docker 管理工具。
docker-compose 和 Docker 差不多,也是只要一份文件就能跑起来。docker-compose 主要的作用就是能够让你不必手敲那么多 Docker 命令
- 安装 - 1 
 2- curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose 
 chmod +x /usr/local/bin/docker-compose
- 目录 建立一个目录,然后在目录中建立docker-compose.yml,内容如下: - 1 
 2
 3
 4
 5
 6
 7
 8- version: "3.7" # 这个是配置文件的版本,不同的版本号声明方式会有细微的不同 
 services:
 info:
 container_name: hello-docker
 image: cosyer/hello-docker:1.0.0
 ports:
 - "8082:80"
 restart: on-failure
- 启动 - 1 - docker-compose up info - docker-compose 会帮我们自动去拉镜像,创建容器,将容器中的80端口映射为宿主机的8082端口。restart字段还要求 docker-compose 当发现容器意外挂掉时 重新启动容器,类似于 pm2,所以你不必再在容器内使用 pm2 
如果想要更新一个镜像创建新容器,只需要:
1
2
3
4docker-compose pull info
docker-compose stop info
docker-compose rm info
docker-compose up -d info # -d 代表后台运行
 
        