---
title: 3.1、Docker(1)-初见
date: 2018-2-8 22:38:32
tags: 
  - linux
  - docker
categories: 
  - linux
---

**程序部署运维的痛点**
当今软件越发庞大复杂 , 在服务器部署运行一个软件之前通常需要完成:
1. 操作系统的设置
2. 各种库和组件的安装

只有他们都正确 , 软件才能正常运行
当需要迁移的时候 , 这些事情都要重来一遍
并且由于各种原因 , 还可能会产生不一样的问题 , 费时费力
<!-- more -->
于是就有了独立运行容器的需求 , 从根本上解决这个问题
让软件带环境安装
给软件一个独立的环境去运行 , 并且这个软件所有的依赖都在这个环境里面


**虚拟机**
虚拟机是一种针对上述问题的解决方案 , 在一个操作系统里面构造一个虚拟环境运行另一个操作系统
但是通常会有以下缺陷
1. 资源占用多
虚拟机会独占一部分内存和硬盘空间。它运行的时候，其他程序就不能使用这些资源了。哪怕虚拟机里面的应用程序，真正使用的内存只有 1MB，虚拟机依然需要几百 MB 的内存才能运行。

2. 冗余步骤多
虚拟机是完整的操作系统，一些系统级别的操作步骤，往往无法跳过，比如用户登录。

3. 启动慢
启动操作系统需要多久，启动虚拟机就需要多久。可能要等几分钟，应用程序才能真正运行。

**Docker**
由于虚拟机存在这些缺点，Linux 发展出了另一种虚拟化技术：Linux 容器
Linux 容器不是模拟一个完整的操作系统，而是对进程进行隔离。或者说，在正常进程的外面套了一个保护层。对于容器里面的进程来说，它接触到的各种资源都是虚拟的，从而实现与底层系统的隔离。

Docker 属于 Linux 容器的一种封装，提供简单易用的容器使用接口。

Docker 将应用程序与该程序的依赖，打包在一个文件里面。运行这个文件，就会生成一个虚拟容器。程序在这个虚拟容器里运行

ubuntu系统直接使用apt安装即可
```bash
apt install docker.io
```

### 镜像(image)
docker把程序及其依赖 , 打包在image文件里面 , 称之为镜像文件
它可以看做是生成容器的模板 , 一个镜像文件可以生成多个运行容器实例
使用`docker image COMMAND`可以实现对镜像的相关操作

```bash
# 查看本机所有的镜像
docker image ls

# 删除指定镜像
docker image rm [ImageId]
```
![查看所有镜像](/images/linux/查看所有镜像.png)
每个镜像都有一个唯一ID , 是一串hash码
我们可以根据这个ID来对指定的镜像进行操作
当然也不需要必须写完整 , 只要能找到一个唯一的镜像就可以了
比如执行`docker image rm 56a`就可以删除掉express-demo这个镜像了
当然使用`docker image rm express-demo`也是可以的
> docker中的操作大都是这种模式

### 容器(container)
容器就是程序运行的独立虚拟环境了 , 容器由镜像生成
( 镜像可以认为是一种存储的结构 , 而容器才是实际运行的实例 )
```bash
# 查看运行中的容器
docker container ls

# 运行指定的镜像
docker container run [ImageId]

# 运行指定的容器
docker container start [ContainerId]

# 停止指定的容器
docker container stop [ContainerId]

# 强行终止指定的容器
docker container kill [ContainerId]
```
使用`run`每次都会生成一个新的容器文件 , 如果要复用指定的容器 , 可以使用`start`



### Hello World
这是一个官方提供的最简单的镜像 , 可以用来熟悉docker的基本用法
```bash
# 从官方仓库拉取镜像
docker image pull hello-world

# 运行这个image
docker container run hello-world
```
> `docker container run`命令具有自动抓取 image 文件的功能。如果发现本地没有指定的 image 文件，就会从仓库自动抓取。因此，前面的`docker image pull`命令并不是必需的步骤

这个镜像当中的程序就是在控制台输出一段内容 , 是docker的一些基本介绍

> image文件生成的容器实例, 本身也是一个文件
默认情况下即使容器停止运行 , 这个文件也还是在的 , 不会被删除
可以使用`docker container ls -all`来查看所有容器文件
使用`docker container rm [ContainerId]`来删除指定的容器文件

### 尝试制作自己的image并运行
这里用一个简单的nodejs项目作为例子 , 尝试制作一个自己的image
```bash
mkdir express-demo
cd express-demo
npm init
npm install express --save
```
index.js
```javascript
const express = require("express")

var port = 7001
const app = express()

app.get("/", function(req, res){
  res.send("<h1>Hello World</h1>");
})

const server = app.listen(port, function(){
  var port = server.address().port;
  console.log("在%s端口执行监听", port)
})
```

#### 编写Dockerfile
在此之前 , 我们也可以在项目目录里面加一个`.dockerignore`
这个文件表示打包image的时候需要排除在外的内容( 很类似.gitignore )
比如
```
.git
node_modules
```
创建Dockerfile文件
```
FROM node:9.11
COPY . /app
WORKDIR /app
RUN npm install
EXPOSE 7001
```
+ `FORM node:9.11` 继承自官方的node镜像 , 冒号后面是标签(通常是版本号)
+ `COPY . /app` 将当前目录下的所有文件(除了.dockerignore排除的)都拷贝到image文件的app目录下
+ `WORKDIR /app` 工作路径为/app
+ `RUN npm install` 在打包image的时候需要执行的 ( 所以这个nodejs项目的依赖包会被打包进image当中 )
+ `EXPOSE 7001` 运行时暴露出的端口号

#### 打包与运行
打包image
```bash
docker image build -t express-demo:1.0 .
# -t参数是指定该image的名字 , 冒号后面是标签(默认是latest)
# . 表示打包当前目录下的文件
```
运行
```
docker container run --rm -p 8000:7001 -it express-demo:1.0 /bin/bash
```
+ `--rm` 容器停止运行时自动删除容器文件
+ `-p` 表示端口映射 , 这里是将容器的7001端口映射到本地的8000端口
+ `-it` 容器的 Shell 映射到当前的 Shell，然后你在本机窗口输入的命令，就会传入容器
+ **express-demo:1.0** image的名称和标签(当然直接用image的id也可以)
+ **/bin/bash** 容器启动后第一个执行的命令 , 这里启动bash , 保证可以使用shell

执行之后会进入到命令提示符`root@2604657cb46c:/app#`
在这里我们就可以执行**node index.js**来运行程序了

运行之后在外部当然是要通过8000端口来访问

#### 自动化运行
上面的方式在启动容器之后还要手动运行程序 , 还是显得有些繁琐了
我们可以在Dockerfile里面加一个CMD的选项
```
FROM node:9.11
COPY . /app
WORKDIR /app
RUN npm install
EXPOSE 7001
CMD node index.js
```
区别于RUN , CMD是在容器启动的时候执行的 , 而RUN是在打包image的时候执行的

> 当然 , 有了这个入口 , 就可以自由发挥了
比如程序启动比较繁琐 , 完全可以在image里面编写一个shell脚本
然后容器启动的时候运行这个脚本即可


#### 其他常用命令
查看指定容器的输出 , 即容器里面Shell的标准输出
```bash
docker container logs [ContainerId]
```

进入一个正在运行的容器
```bash
docker container exec -it [ContainerID] /bin/bash
```
之后就可以在容器的shell当中执行命令了

有时候我们需要把容器当中运行产生的文件拷贝出来
可以使用
```
docker container cp [ContainerId]:[/app/run.log] /home/sookie
```
上面的命令表示将指定容器的/app/run.log文件拷贝到本地的/home/sookie目录下

#### 使用国内镜像仓库
出于众所周知的原因 , 官方仓库的速度比较慢
所以可以把官方镜像的下载地址改为国内的镜像仓库


Docker 官方中国区
https://registry.docker-cn.com
网易
http://hub-mirror.c.163.com
ustc
https://docker.mirrors.ustc.edu.cn

##### 方法1 registry-mirror参数
直接设置 –registry-mirror 参数，仅对当前的命令有效
```bash
docker run hello-world --registry-mirror=https://docker.mirrors.ustc.edu.cn
```

##### 方法2 修改/etc/default/docker
加入 DOCKER_OPTS=”镜像地址”，可以有多个
```
DOCKER_OPTS="--registry-mirror=https://docker.mirrors.ustc.edu.cn"
```

##### 方法3 修改/etc/docker/daemon.json
```json
{
"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
}
```
修改完成后重启dokcer服务
```bash
systemctl daemon-reload
systemctl restart docker
```
> 新版的docker比较推荐方法3