---
id: "2019-02-01-14-05"
date: "2019/02/01 14:05"
title: "3.从零开始在docker中部署java应用"
tags: ["docker", "springboot", "jar", "war", "elastricsearch", "activemq"]
categories:
  - "linux"
  - "docker教程"
---

# 前言

&emsp;&emsp;在没有 docker 前，项目转测试是比较麻烦的一件事。首先会化较长的时间搭建测试环境，然后在测试过程中又经常出现测试说是 bug，开发说无法复现的情况，导致撕逼。

&emsp;&emsp;本篇记录了如何将一个 java 应用部署到 docker 中。主要讲述了以下几个部分：

- docker 部署 mysql
- docker 部署 activemq
- docker 部署 elastricsearch 集群
- docker 部署 java 应用
- docker 部署 nginx 作为静态服务器，及代理服务器

项目架构如下：

![项目架构](https://raw.githubusercontent.com/FleyX/files/master/blogImg/linux/docker/20190201151250.png)

本系统中有三个主要模块 OMS,DAS 和一个 Eureka 注册中心。其中 OMS 和 DAS 使用有 activemq 消息队列，来进行大量数据的交互然后各自使用一个 mysql 数据库存储主要的业务数据。使用 elastricsearch 存储超大量的数据。

## 传统软件部署和 docker 部署

&emsp;&emsp;本项目在 windows 部署时是将其作为三个部分来进行安装的--ENV 环境包（保护 mysql，es 等），OMS 产品包，DAS 产品包。所以最初我的设想是一个容器中装 ENV 环境包所需的所有软件，一个容器装 DAS，一个容器装 OMS。然后再实践的过程中越来越感觉不对劲，环境配置比较复杂，而且也有种把容器当虚拟机用的感觉，一点没有简化的感觉。

<!-- more -->

&emsp;&emsp;遂停下了操作，开始学习一波 docker 到底是怎么用的。用租房子来做比喻：

- 传统软件部署方式相当于租到一个零家具，零装修的房子。我们想要住进去首先必须买齐必要的家具，然后想要住的舒心呢，还得花功夫装饰装饰，让房子好看点。这样就会对这个具体的房子产生较强的依赖，很难迁移到另一个房子中（想想那么多的家具，家电，杂物。。）。如果全部放弃重新换一个房子代价又太大了。

![搬家](https://raw.githubusercontent.com/FleyX/files/master/blogImg/linux/docker/20190201161044.png)

- 使用 docker 相当于租到一个全家电，精装修的房子。我们只需带上自己的个人物品即可开始入住。想要换一个房子也是轻而易举，带上自己的东西麻溜的就换了。

![轻松搬家](https://raw.githubusercontent.com/FleyX/files/master/blogImg/linux/docker/20190201161301.png)

&emsp;&emsp;使用 docker 推荐操作是一个进程放到一个容器中，做到更好的隔离性，同时也更容易进行管理。下面来使用容器技术部署我们应用。还是分为三部分，但是每个进程使用一个容器，做到 0 配置启动容器。

# 实战

&emsp;&emsp;在此默认已经会安装 docker，且了解基本操作。如不了解的先看这两篇：[安装](https://blog.fleyx.com/blog/detail/2018-11-20-10-38-05)，[基本使用](https://blog.fleyx.com/blog/detail/2018-12-26-13-18)

## 部署 ENV 环境包

&emsp;&emsp;环境包中诸如 elastricsearch，mysql 这样的数据存储工具，需要满足如下两个要求：

1. 保留数据，不论容器如何创建、销毁，数据不能丢。
2. 需要使用个性化的配置文件，每次启动根据该配置文件来启动。

### 部署 mysql

&emsp;&emsp;使用容器部署 mysql 过程如下：

1. 首先从 docker.hub 中根据各自的需求 pull 对应的 mysql 镜像

```bash
docker pull mysql:5.7.24
```

2. 启动镜像

&emsp;&emsp;由于 mysql 是用来存数据的，数据无论什么情况都不能丢失，所以数据存在容器外部，通过映射操作，映射到容器内部，参数如下：

```bash
# 将宿主机的路径，映射到容器内部。这个路径既可以是文件夹，也可以是文件
-v hostPath:containerPath
```

显然我们通过这个桉树将外部的数据文件夹，配置文件映射到容器中。最后启动这个容器的命令如下：

```bash
# 假设在宿主机中数据存放路径为/opt/mysql/data,配置文件路径为：/opt/mysql/my.cnf
docker run --name=mysql -itd -p 3308:3306 -v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone -v /opt/mysql/data:/var/lib/mysql -v /opt/mysql/my.cnf:/etc/mysql/my.cnf -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7.25
```

**注意：如果 mysql 版本为 8.x，还需要映射容器中的/var/lib/mysql-files 目录，否则启动会报错**

下面介绍具体参数含义：

- -it 标准输入输出有关
- -d 后台启动
- -v 文件映射
- -e 设置环境变量到容器中

&emsp;&emsp;可能你们会问为什么要映射`/etc/timezone`和`/etc/timezone`，这是为了让容器的时间和时区与宿主机保持一致。默认情况下容器为 UTC 标准时间。`/etc/timezone`让容器时间，时区和宿主机一致。但是如果不映射`/etc/timezone`java 应用中的时区还是错的，虽然使用`date -R`命令查看时间和时区都正常。

### 部署 elastricsearch,activeMQ 容器

&emsp;&emsp;es 和 activeMQ 都依赖 java 的运行环境，所以有两种部署方式：

- 直接拉取 es，activeMQ 对应镜像，通过路径映射部署容器启动
- 在一个 java 镜像中运行 es 和 activeMQ

&emsp;&emsp;这里以第二种方式为例进行说明。

#### 创建 java 镜像

&emsp;&emsp;这里不从 docker hub 中拉取镜像，通过 dockerfile 来制作一个自定义的镜像。由于只需要一个 java 运行环境，所以只要将一个 jre 运行环境加入到一个基础 linux 镜像中即可（这里选择 ubuntu）。制作过程如下：

&emsp;&emsp;首先创建一个文件夹`dockerFileTest`存放依赖和 dockerfile 文件。

&emsp;&emsp;然后将下载加压后的 jre 运行环境放到`dockerFileTest/jre`目录下。

&emsp;&emsp;接着在 dockerFileTest 目录中创建 Dockerfile 文件,内容如下：

```bash
#说明基础镜像，默认:latest
FROM ubuntu
#将当前路径下的jre文件夹复制到新镜像下的/opt/jre路径
COPY jre /opt/jre
#设置环境变量
ENV JAVA_HOME=/opt/jre CLASSPATH=/opt/jre/lib PATH=$PATH:/opt/jre/bin
```

&emsp;&emsp;最后通过``命令生成新镜像 jre:v1

#### 创建 elastricsearch 容器

&emsp;&emsp;下载好 es，假设存放在/root/es1 中，通过以下命令创建一个 es 容器：

```bash
docker run --name=es1 -itd -p 9200:9200 -p 9300:9300 -v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone -v /root/es1:/opt/es -w /opt/es jre:v1 ./bin/elasticsearch -Des.insecure.allow.root=true
```

参数含义如下：

- -w containerPath 设置容器工作目录为 containerPath

&emsp;&emsp;上面的命令以 es1 为容器名，映射 9200，9300 到宿主机端口，以`./bin/'elasticearch -Des.insecure.allow.root=true`创建 es 容器。最后加的参数为了让 es 能够以 root 在容器中启动。

#### 创建 es 集群

&emsp;&emsp;将之前的 es1 复制一份命名为 es2 作为节点 2。要让两个 es 节点构成 es 集群，需要让节点间能够进行通信，这里使用`--link`参数来让 es2 能够连上 es1 构成集群。`--link`用法如下：

```bash
--link containerName[:alias]
```

es2 启动命令如下：

```bash
docker run --name=es2 -itd -p 9201:9200 -p 9301:9300 --link es1 -v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone -v /root/es2:/opt/es -w /opt/es jre:v1 ./bin/elasticsearch -Des.insecure.allow.root=true
```

然后就能在 es2 中通过 es1 的容器名访问到 es1（实际是在 es2 的 host 中增加了一条记录，将 es1 指向 es1 的 IP，该 IP 是 docker 的虚拟网卡分配的 IP）。

&emsp;&emsp;但是使用--link 有一些局限,通过该参数联通的容器必须存在。因此该参数只能用在 B 依赖 A 的情况，如果同时 A 也依赖 B(也就是 A，B 要能够相互访问到),这种情况下就不能通过 link 来实现了，原因大家应该能够想到。。。

#### 部署 activeMQ

&emsp;&emsp;在容器中启动 activeMQ 与启动 es 稍有不同。activeMQ 默认是后台启动的，启动完成后启动程序就会退出，因此如果直接以`./bin/activemq start`(假设当前目录在 activemq 中),启动容器会发现在 activemq 启动成功后容器就停止运行了。会出现这种情况是因为`容器中启动的第一个进程结束后，容器就会被docker关闭掉`。所以呢我们只需让第一个进程不结束就行了，因此需要我们编写一个启动脚本来启动 activemq 并监测运行情况，一旦 activemq 进程挂掉，就结束启动脚本，否则一直运行。启动脚本代码如下：

```bash
#!/bin/bash

#使用sh脚本启动activemq，然后定时判断服务是否被关闭，关闭后退出脚本，否则一致循环。
#为避免docker容器在active自带的启动脚本运行结束后就关闭容器了。

#获取启动pid
out=`./bin/activemq start`
echo "$out"

pid=`echo $out | grep -Eo "pid '[0-9]+'" | grep -Eo "[0-9]+"`

echo "当前mq进程pid为：${pid}"

if [ ${#pid} = 0 ]; then
  echo "启动失败"
  exit 0
fi

while true; do
  num=`ps -e | grep -cwE "$pid"`
  if [ $num = 0 ]; then
    echo "进程异常关闭"
    exit 0
  fi
  sleep 1
done
```

&emsp;&emsp;然后以该脚本作为启动脚本来启动容器即可。启动命令如下：(假设 activemq 目录为/opt/activemq,启动脚本路径为/opt/activemq/start.sh)

```bash
docker run --name=activemq -itd -p 8161:8161 -p 61616:61616 -v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone -v /root/activemq:/opt/activemq -w /opt/activemq jre:v1 /bin/bash ./start.sh
```

## 部署 java 环境包

&emsp;&emsp;还是使用之前制作的 jar 镜像来启动 java 应用，这里以部署 jar 包为例，如果部署 war 包则需要在 tomcat 镜像上部署。特别注意下容器的时间和时区设置，否则 java 程序中无法获取到正确的时间和时区。这里通过映射宿主机的 localtime 和 timezone 文件来让容器时间和时区与宿主机相同。启动命令如下：

```bash
# 启动oms
docker run -itd --name=oms -p 8082:9090 -v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone -v /root/oms.jar:/opt/oms.jar  -w /opt --link es1 --link activemq --link oms-mysql --link eureka-server  jre:v1  java -jar oms.jar

# 启动das
docker run -itd --name=das -p 8083:9099 -v /etc/localtime:/etc/localtime -v /etc/timezone:/etc/timezone -v /root/das.jar:/opt/oms  -w /opt --link es1 --link activemq --link oms-mysql --link eureka-server  jre:v1  java -jar das.jar
```

&emsp;&emsp;本篇只是记录了如何使用`一容器一进程`的方式来部署 java 应用.

**PS:不推荐这么直接手撸命令，建议使用 docker-compose**

