Docker

DevOpsDocker

# 一、Docker基础

# 1.1 Docker介绍

Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的 cgroup,namespace,以及 AUFS 类的 Union FS 等技术,对进程进行封装隔离,属于 操作系统层面的虚拟化技术。

由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。

Docker的沙盒机制,每一个运行的docker容器都有其独立的运行环境及网络;从文件系统、网络互联到进程隔离。

# 1.2 Docker特点

  • 更高效的利用系统资源

    由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker 对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以运行更多数量的应用。

  • 更快速的启动时间

    传统的虚拟机技术启动应用服务往往需要数分钟,而 Docker 容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。

  • 持续交付和部署

    对开发和运维(DevOps)人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。

    使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员可以通过 Dockerfile 来进行镜像构建,并结合 持续集成(Continuous Integration) 系统进行集成测试,而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合 持续部署(Continuous Delivery/Deployment) 系统进行自动部署。

    而且使用 Dockerfile 使镜像构建透明化,不仅仅开发团队可以理解应用运行环境,也方便运维团队理解应用运行所需条件,帮助更好的生产环境中部署该镜像。

  • 更轻松的迁移

    由于 Docker 确保了执行环境的一致性,使得应用的迁移更加容易。Docker 可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。

  • 更轻松的维护和扩展

    Docker 使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,Docker 团队同各个开源项目团队一起维护了一大批高质量的 官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的镜像制作成本。

# 1.3 Docker架构

标题 说明
镜像(Images) Docker 镜像是用于创建 Docker 容器的模板。
容器(Container) 容器是独立运行的一个或一组应用。
客户端(Client) Docker 客户端通过命令行或者其他工具使用 Docker API (https://docs.docker.com/reference/api/docker_remote_api (opens new window)) 与 Docker 的守护进程通信。
主机(Host) 一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。
仓库(Registry) Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。Docker Hub(https://hub.docker.com (opens new window)) 提供了庞大的镜像集合供使用。
Docker Machine Docker Machine是一个简化Docker安装的命令行工具,通过一个简单的命令行即可在相应的平台上安装Docker,比如VirtualBox、 Digital Ocean、Microsoft Azure。

# 二、Docker安装

# 2.1 安装Docker(Ubuntu)

# 下载Docker

sudo apt-get update
# 安装必要的一些系统工具
sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common
# 安装 GPG 证书
sudo curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
# 写入软件源信息
sudo add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
# 更新并安装 Docker CE
sudo apt-get -y update
sudo apt-get -y install docker-ce
1
2
3
4
5
6
7
8
9
10

以上命令会添加稳定版本的 Docker CE APT 镜像源,如果需要最新或者测试版本的 Docker CE 请将 stable 改为 edge 或者 test。从 Docker 17.06 开始,edge test 版本的 APT 镜像源也会包含稳定版本的 Docker。

# 启动 Docker CE

sudo systemctl enable docker

sudo systemctl start docker

sudo docker version
1
2
3
4
5

# 建立 docker 用户组

默认情况下,docker 命令会使用 Unix socket 与 Docker 引擎通讯。

而只有 root 用户和 docker 组的用户才可以访问 Docker 引擎的 Unix socket。

出于安全考虑,一般 Linux 系统上不会直接使用 root 用户。

因此,更好地做法是将需要使用 docker 的用户加入 docker 用户组。

建立 docker 组:

sudo groupadd docker
1

将当前用户加入 docker 组:

sudo usermod -aG docker $USER
1

退出当前终端并重新登录,进行如下测试。

sudo docker info
1

# Docker 镜像加速器

国内从 Docker Hub 拉取镜像有时会遇到困难,此时可以配置镜像加速器。

Docker 官方和国内很多云服务商都提供了国内加速器服务,例如:

编辑文件:

sudo vi /etc/docker/daemon.json
1

调整如下:

{
  "registry-mirrors": [
    "http://mirrors.aliyun.com"
  ]
}
1
2
3
4
5

注意,一定要保证该文件符合 json 规范,否则 Docker 将不能启动。

补充:设置自己的阿里云镜像加速器

{
  "registry-mirrors": [
    "https://zy2rj443.mirror.aliyuncs.com"
  ]
}
1
2
3
4
5

zy2rj443为阿里云为每个人分配的镜像加速器。

获取步骤:

1、在阿里云搜索 容器镜像服务

2、点击镜像中心——镜像加速器

重启docker服务

sudo systemctl daemon-reload
sudo systemctl restart docker
sudo docker info
1
2
3

# 2.2 安装docker(CentOS7)

使用【uname -a】命令确定你是CentOS7及以上版本,输出如下:

Linux SERVER-235 3.10.0-957.el7.x86_64 #1 SMP Thu Nov 8 23:39:32 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
1

yum安装gcc相关

yum -y install gcc
yum -y install gcc-c++
1
2

安装需要的软件包

yum install -y yum-utils
1

设置stable镜像仓库

yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
1

更新yum 软件包索引

yum makecache fast
1

安装DOCKER CE

yum install docker-ce docker-ce-cli containerd.io
1

启动docker,没有报错及成功

systemctl start docker
systemctl enable docker
docker version
1
2
3

# 安装docker-compose

二进制包安装

​ 在 Linux 上的也安装十分简单,从 官方 GitHub Release 处直接下载编译好的二进制文件即可。

sudo curl -L https://github.com/docker/compose/releases/download/1.24.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
# 赋予可执行权限
sudo chmod +x /usr/local/bin/docker-compose
1
2
3

若使用非root账户,执行如下命令

sudo touch /usr/local/bin/docker-compose
# 赋予最大权限
sudo chmod 777 /usr/local/bin/docker-compose

sudo curl -L https://github.com/docker/compose/releases/download/1.24.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
1
2
3
4
5

查看版本

sudo docker-compose version
1

卸载

​ 如果是二进制包方式安装的,删除二进制文件即可。

sudo rm /usr/local/bin/docker-compose
1

# 三、Docker命令

# 3.1 Docker基础命令

启动docker

systemctl start docker
1

关闭docker

systemctl stop docker
1

重启docker

systemctl restart docker
1

docker设置随服务启动而自启动

systemctl enable docker
1

查看docker 运行状态

------如果是在运行中 输入命令后 会看到绿色的active

systemctl status docker
1

查看docker 版本号信息

docker version

docker info
1
2
3

# 3.2 Docker镜像命令

docker官方镜像 (opens new window)

查看自己服务器中docker 镜像列表

docker images
1

搜索镜像

docker search 镜像名
docker search --filter=STARS=9000 mysql 搜索 STARS >9000的 mysql 镜像
1
2

拉取镜像

不加tag(版本号) 即拉取docker仓库中 该镜像的最新版本latest 加:tag 则是拉取指定版本

docker pull 镜像名 
docker pull 镜像名:tag
1
2

删除镜像

------当前镜像没有被任何容器使用才可以删除

#删除一个
docker rmi -f 镜像名/镜像ID

#删除多个 其镜像ID或镜像用用空格隔开即可 
docker rmi -f 镜像名/镜像ID 镜像名/镜像ID 镜像名/镜像ID

#删除全部镜像  -a 意思为显示全部, -q 意思为只显示ID
docker rmi -f $(docker images -aq)

#强制删除镜像
docker image rm 镜像名称/镜像ID
1
2
3
4
5
6
7
8
9
10
11

# 3.3 Docker清理命令

  • 清理虚悬镜像image
docker image prune
1
  • 清理 数据卷Volumes数据
# 查看Docker磁盘空间
docker system df
# 批量删除虚悬volume
docker volume rm $(docker volume ls -q)
# 查看所有volume
docker volume ls
1
2
3
4
5
6

# 3.4 Docker容器命令

查看正在运行容器列表

docker ps
1

查看所有容器

-----包含正在运行 和已停止的

docker ps -a
1

-- 过滤查询

docker ps --filter name=ccc
1

运行一个容器

# -it 表示 与容器进行交互式启动 -d 表示可后台运行容器 (守护式运行)  --name 给要运行的容器 起的名字  /bin/bash  交互路径
docker run -it -d --name 要取的别名 镜像名:Tag /bin/bash 
1
2

启动容器

docker start 容器ID/容器名
1

停止容器

# 先停止咱之前运行的 redis 容器 
docker stop 容器名/容器ID
1
2

重启容器

docker restart 容器ID/容器名
1

删除容器

#删除一个容器
docker rm -f 容器名/容器ID

#删除多个容器 空格隔开要删除的容器名或容器ID
docker rm -f 容器名/容器ID 容器名/容器ID 容器名/容器ID

#删除全部容器
docker rm -f $(docker ps -aq)
1
2
3
4
5
6
7
8

进入容器方式

docker exec -it 容器名/容器ID /bin/bash
1

查看容器日志

docker logs -f --tail=要查看末尾多少行 默认all 容器ID
1

# 四、Docker网络

Docker的网络模块是可插拔式的,提供了3种网络模式可以选择。

  • host模式
  • container模式
  • none模式
  • bridge模式

生成容器时不指定网络模式下,默认使用bridge桥接模式

通过docker network ls这个命令来查看本机中所有的网络模式。

# 4.1 container模式

模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享。

新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。

# 4.2 none模式

使用none模式,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。

也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。这种网络模式下容器只有lo回环网络,没有其他网卡。

none模式可以在容器创建时通过–network=none来指定。这种类型的网络没有办法联网,封闭的网络能很好的保证容器的安全性。

# 4.3 Host模式

Docker中的host模式指定是容器与主机享受相同的network namespace。

默认Docker容器运行会分配独立的Network Namespace隔离子系统,而基于host模式,容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个Network Namespace,容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。(用的是宿主机的IP,也就是和宿主机共用一个IP地址,host模式不需要加-p进行端口映射,因为和宿主机共享网络IP和端口)

在这种情况下,我们访问主机端口就能访问我们的容器。比如说我们运行tomcat容器并且用-- network=host 来指定我们的网络模式为host,这样我们访问本机的8080端口就能访问到我们的tomcat容器。

# 4.4 Bridge模式

bridge模式是docker的默认网络模式,不写–net参数,就是bridge模式。

当Docker进程启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器都会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。

# 五、Docker部署

# 六、Docker FAQ

# 6.1 Docker配置网络为host模式

问题描述:

在Linux上使用host模式遇到了在宿主机上能访问,但是通过其他机器无法访问,telnet也不通。

原因:

主要是因为host模式使用宿主机网络,因此需要在指定区域中主动开放防火墙端口;

而使用bridge、overlay模式时,这些接口默认都添加到firewalld的docker(ACCEPT)区域中。

解决方案:

第一种:关闭防火墙;

#在开机时禁用
systemctl disable firewalld.service
#停止
systemctl stop firewalld
1
2
3
4

第二种:若防火墙不能关闭,则开发端口;

例:CentOS7

# 开放5672端口
firewall-cmd --zone=public --add-port=5672/tcp --permanent
#更新防火墙规则
firewall-cmd --reload
#查看防火墙所有开放的端口
firewall-cmd --zone=public --list-ports
1
2
3
4
5
6

# 6.2 Docker部署时间差8小时问题

# 6.2.1 前置-时区

UTC Coordinated Universal Time 世界统一时间,世界标准时间,协调世界时

GMT Greenwish Mean Time 格林威治平时,

CTT China Shanghai Time 东八区时间,上海时间,北京时间

CST China Standard Time 中国沿海时间(北京时间)

UTC标准时间,是协调世界时

GMTUTC的民间名称。GMT=UTC

中国在东八区,用CTT标识,也就是UTC+8CTT=UTC+8=GMT+8

linux查看时间命令

命令 说明
date 查看当前日期时间
date -R 查看时区时间
date -u 查看UTC时间
# date
Sat Dec  3 20:00:08 CST 2022
# date -R
Sat, 03 Dec 2022 20:00:08 +0800
# date -u
Sat Dec  3 12:00:08 UTC 2022
1
2
3
4
5
6
  • CST标识中国北京时间
  • UTC 标识世界标准时间

java查看ZoneId

java.time.ZoneId类中,配置了全球各区域的时间配置。

map.put("CTT", "Asia/Shanghai");
1

# 6.2.2 问题描述

docker启动容器,运行jar程序,总会遇见各种各样的8小时时差问题(时区问题)。

  1. 容器时差:启动docker容器与挂载主机时间相差8小时;
  2. java程序运行时差:
  3. logback日志输出时差:
  4. mysql插入时差:使用LocalDateTime插入到数据库,时间少了8小时;
  5. 前后端交互序列化时差:前端、后端传递时间,相差8小时;

# 6.2.3 解决Docker主机与容器时差

容器时间与主机差8个小时,是因为主机的与容器的/etc/localtime不一致。

解决方案:在构建镜像时,指定时间。

将主机的Shanghai 时区内容,复制到容器的/etc/localtime文件。

FROM openjdk:8-jre
# 时间
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 挂载数据卷
。。。
1
2
3
4
5

效果:保证主机的时间与容器时间一致。如下表示配置成功,时间已保持一致。

主机:

# 在主机执行,查看时间date
root@k-docker:~# date
Sat Dec  3 17:48:41 CST 2022
1
2
3

容器内部:

# 在主机执行,进入容器内部
docker exec -it 9262e49aaf0c /bin/bash

# 在容器内部执行,查看时间date
root@k-docker:/# date
Sat Dec  3 17:47:36 CST 2022
1
2
3
4
5
6
  • CST标识中国北京时间

# 6.2.4 解决java程序运行时差

解决方案一:SpringBoot项目在启动类中配置时区。

public static void main(String[] args) {
	// 启动类中,配置时区
    TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
    SpringApplication.run(InspectionApplication.class, args);
}
1
2
3
4
5

解决方案二:通过jvm参数配置时区。

-Duser.timezone=GMT+08
1

解决方案三:在启动容器时挂载时区。

因为Java获取时间是从/etc/timezone里获取时区,所以将该文件挂载至容器内部,替换为上海时区

  • 若是通过docker-compose进行编排容器,则在docker-compose.yml中进行配置volumes

    version: '3.3'
    services:
        my-server:
            restart: unless-stopped
            image: my-server:1.1.0
            container_name: my-server
            network_mode: host
            volumes:
                - ./logs:/logs:rw
                - /etc/localtime:/etc/localtime:ro
                - /etc/timezone:/etc/timezone:ro
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  • 若是通告docker run启动容器,则在启动命令中添加:

    docker run -d -p 80:80 -v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone nginx
    
    1

    说明:

    如果挂载主机没有/etc/timezone文件,则新建:

    echo "Asia/shanghai" > /etc/timezone;
    
    1

效果:保证主机的timezone与容器的timezone一致。

如下表示未配置,目前时区不一致。主机在上海时区,而容器内部是在

主机:

root@k-docker:~# cat /etc/timezone 
Asia/Shanghai
1
2

容器:

root@k-docker:/# cat /etc/timezone 
Etc/UTC
1
2
  • Asia/Shanghai 中国时区
  • Etc/UTC 协调世界时

# 6.2.5 解决logback日志输出时差

解决方案一:在pattern中的%d日期配置中,添加CTT,指定时区为东八区。

<appender name="logFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <encoder>
        <!-- %p日志级别,%t线程名,%d日期,%c类的全名,%方法名,%L行号,%m输出的信息 -->
        <pattern>
            [%d{yyyy-MM-dd HH:mm:ss.SSS,CTT}] %-5level [%t] %class{36}.%M - %L - %msg%xEx%n
        </pattern>
        <!-- 记录日志的编码 -->
        <charset>UTF-8</charset>
    </encoder>
</appender>
1
2
3
4
5
6
7
8
9
10

# 6.2.6 解决mysql插入时差

解决方案一:在jdbc连接配置

url: jdbc:mysql://127.0.0.1:3306/database_test?serverTimezone=Asia/Shanghai

url: jdbc:mysql://127.0.0.1:3306/database_test?serverTimezone=CTT
1
2
3

# 6.2.7 前后端交互序列化时差

解决方案一:使用jackson进行序列化时,如下统一配置时区。

@Bean
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
    ObjectMapper objectMapper = builder.createXmlMapper(false).build();
    /*
     * 设置为东八区的时区。此处配置了,@JsonFormat注解则可省略时区配置;
     */
    objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
}
1
2
3
4
5
6
7
8

解决方案二:使用@JsonFormat注解指定配置时区。

import com.fasterxml.jackson.annotation.JsonFormat;

public class Bean {
    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    private Date time;
}
1
2
3
4
5
6