微服务实战 – docker-compose实现mysql+springboot+angular

1次阅读

共计 6520 个字符,预计需要花费 17 分钟才能阅读完成。

前言
这是一次完整的项目实践,Angular 页面 +Springboot 接口 +MySQL 都通过 Dockerfile 打包成 docker 镜像,通过 docker-compose 做统一编排。目的是实现整个项目产品的轻量级和灵活性,在将各个模块的镜像都上传公共镜像仓库后,任何人都可以通过“docker-compose up -d”一行命令,将整个项目的前端、后端、数据库以及文件服务器等,运行在自己的服务器上。本项目是开发一个类似于 segmentfault 的文章共享社区,我的设想是当部署在个人服务器上时就是个人的文章库,部署在项目组的服务器上就是项目内部的文章库,部署在公司的服务器上就是所有职工的文章共享社区。突出的特点就是,项目相关的所有应用和文件资源都是灵活的,用户可以傻瓜式地部署并使用,对宿主机没有任何依赖。目前一共有三个 docker 镜像,考虑以后打在一个镜像中,但目前只能通过 docker-compose 来编排这三个镜像。

MySQL 镜像:以 MySQL 为基础,将项目所用到的数据库、表结构以及一些基础表的数据库,通过 SQL 脚本打包在镜像中。用户在启动镜像后就自动创建了项目所有的数据库、表和基础表数据。
SpringBoot 镜像:后台接口通过 SpringBoot 开发,开发完成后直接可以打成镜像,由于其内置 tomcat,可以直接运行,数据库指向启动好的 MySQL 容器中的数据库。
Nginx(Angular)镜像:Nginx 镜像中打包了 Angular 项目的 dist 目录资源,以及 default.conf 文件。主要的作用有:部署 Angular 项目页面;挂载宿主机目录作为文件服务器;以及反向代理 SpringBoot 接口,解决跨域问题等等。

最后三个 docker 容器的编排通过 docker-compose 来实现,三个容器之间的相互访问都通过容器内部的别名,避免了宿主机迁移时 ip 无法对应的问题。为了方便开发,顺便配了个自动部署。
MySQL 镜像
初始化脚本
在项目完成后,需要生成项目所需数据库、表结构以及基础表数据的脚本,保证在运行该 docker 容器中,启动 MySQL 数据库时,自动构建数据库和表结构,并初始化基础表数据。Navicat for MySQL 的客户端支持导出数据库的表结构和表数据的 SQL 脚本。如果没有安装 Navicat,可以在连接上容器中开发用的 MySQL 数据库,通过 mysqldump 命令导出数据库表结构和数据的 SQL 脚本。下文中就是将数据库的 SQL 脚本导出到宿主机的 /bees/sql 目录:
docker exec -it  mysql mysqldump -uroot -pPASSWORD 数据库名称 > /bees/sql/ 数据库名称.sql

以上只是导出 表结构和表数据的脚本,还要在 SQL 脚本最上方加上 生成数据库的 SQL:
drop database if exists 数据库名称;
create database 数据库名称;
use 数据库名称;
通过以上两个步骤,数据库、表结构和表数据三者的初始化 SQL 脚本就生成好了。
Dockerfile 构建镜像
我们生成的 SQL 脚本叫 bees.sql, 在 MySQL 官方镜像中提供了容器启动时自动执行 /docker-entrypoint-initdb.d 文件夹下的脚本的功能 (包括 shell 脚本和 sql 脚本),我们在后续生成镜像的时候,将上述生成的 SQL 脚本 COPY 到 MySQL 的 /docker-entrypoint-initdb.d 目录下就可以了。现在我们写 Dockerfile,很简单:
FROM mysql

MAINTAINER kerry(kerry.wu@definesys.com)

COPY bees.sql /docker-entrypoint-initdb.d
将 bees.sql 和 Dockerfile 两个文件放在同一目录,执行构建镜像的命令就可以了:
docker build -t bees-mysql .
现在通过 docker images,就能看到本地的镜像库中就已经新建了一个 bees-mysql 的镜像啦。
SpringBoot 镜像
springboot 构建镜像的方式很多,有通过代码生成镜像的,也有通过 jar 包生成镜像的。我不想对代码有任何污染,就选择后者,通过生成的 jar 包构建镜像。创建一个目录,上传已经准备好的 springboot 的 jar 包,这里名为 bees-0.0.1-SNAPSHOT.jar,然后同样编写 Dockerfile 文件:
FROM java:8
VOLUME /tmp
ADD bees-0.0.1-SNAPSHOT.jar /bees-springboot.jar
EXPOSE 8010
ENTRYPOINT [“java”,”-Djava.security.egd=file:/dev/./urandom”,”-jar”,”-Denv=DEV”,”/bees-springboot.jar”]
将 bees-0.0.1-SNAPSHOT.jar 和 Dockerfile 放在同一目录执行命令开始构建镜像,同样在本地镜像库中就生成了 bees-springboot 的镜像:
docker build -t bees-springboot .
Nginx(Angular)镜像
Nginx 的配置
该镜像主要在于 nginx 上 conf.d/default.conf 文件的配置,主要实现三个需求:
1、Angualr 部署 Angular 的部署很简单,只要将 Angular 项目通过 ng build –prod 命令生成 dist 目录,将 dist 目录作为静态资源文件放在服务器上访问就行。我们这里就把 dist 目录打包在 nginx 容器中,在 default.conf 上配置访问。2、文件服务器项目为文章共享社区,少不了的就是一个存储文章的文件服务器,包括存储一些图片之类的静态资源。需要在容器中创建一个文件目录,通过 default.conf 上的配置将该目录代理出来,可以直接访问目录中的文件。当然为了不丢失,这些文件最好是保存在宿主机上,在启动容器时可以将宿主机本地的目录挂载到容器中的文件目录。3、接口跨域问题在前后端分离开发的项目中,“跨域问题”是较为常见的,SpringBoot 的容器和 Angular 所在的容器不在同一个 ip 和端口,我们同样可以在 default.conf 上配置反向代理,将后台接口代理成同一个 ip 和端口的地址。
话不多说,结合上面三个问题,我们最终的 default.conf 为:
server {
listen 80;

server_name localhost;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}

location /api/ {
proxy_pass http://beesSpringboot:8010/;
}

location /file {
alias /home/file;
index index.html index.htm;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

location / : 代理的是 Angular 项目,dist 目录内通过 Dockerfile COPY 在容器内的 /usr/share/nginx/html 目录;
location /file:代理 /home/file 目录,作为文件服务器;
location /api/ : 是为了解决跨域而做的反向代理,为了脱离宿主机的限制,接口所在容器的 ip 通过别名 beesSpringboot 来代替。别名的设置是在 docker-compose.yml 中设置的,后续再讲。

Dockerfile 构建镜像
同样创建一个目录,包含 Angualr 的 dist 目录、Dockerfile 和 nginx 的 default.conf 文件,目录结构如下:
[root@Kerry angular]# tree
.
├── dist
│   └── Bees
│   ├── 0.cb202cb30edaa3c93602.js
│   ├── 1.3ac3c111a5945a7fdac6.js
│   ├── 2.99bfc194c4daea8390b3.js
│   ├── 3.50547336e0234937eb51.js
│   ├── 3rdpartylicenses.txt
│   ├── 4.53141e3db614f9aa6fe0.js
│   ├── assets
│   │   └── images
│   │   ├── login_background.jpg
│   │   └── logo.png
│   ├── favicon.ico
│   ├── index.html
│   ├── login_background.7eaf4f9ce82855adb045.jpg
│   ├── main.894e80999bf907c5627b.js
│   ├── polyfills.6960d5ea49e64403a1af.js
│   ├── runtime.37fed2633286b6e47576.js
│   └── styles.9e4729a9c6b60618a6c6.css
├── Dockerfile
└── nginx
└── default.conf
Dockerfile 文件如下:
FROM nginx

COPY nginx/default.conf /etc/nginx/conf.d/

RUN rm -rf /usr/share/nginx/html/*

COPY /dist/Bees /usr/share/nginx/html

CMD [“nginx”, “-g”, “daemon off;”]
以上,通过下列命令,构建 bees-nginx-angular 的镜像完成:
docker build -t bees-nginx-angular .
docker-compose 容器服务编排
上述,我们已经构建了三个镜像,相对应的至少要启动三个容器来完成项目的运行。那要执行三个 docker run?太麻烦了,而且这三个容器之间还需要相互通信,如果只使用 docker 来做的话,不光启动容器的命令会很长,而且为了容器之间的通信,docker –link 都会十分复杂,这里我们需要一个服务编排。docker 的编排名气最大的当然是 kubernetes,但我的初衷是让这个项目轻量级,不太希望用户安装偏重量级的 kubernetes 才能运行,而我暂时又没能解决将三个镜像构建成一个镜像的技术问题,就选择了适中的一个产品 –docker-compse。安装 docker-compose 很简单,这里就不赘言了。安装完之后,随便找个目录,写一个 docker-compose.yml 文件,然后在该文件所在地方执行一行命令就能将三个容器启动了:
#启动
docker-compose up -d
#关闭
docker-compose down
这里直接上我写的 docker-compose.yml 文件
version: “2”
services:

beesMysql:
restart: always
image: bees-mysql
ports:
– 3306:3306
volumes:
– /bees/docker_volume/mysql/conf:/etc/mysql/conf.d
– /bees/docker_volume/mysql/logs:/logs
– /bees/docker_volume/mysql/data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: kerry

beesSpringboot:
restart: always
image: bees-springboot
ports:
– 8010:8010
depends_on:
– beesMysql

beesNginxAngular:
restart: always
image: bees-nginx-angular
ports:
– 8000:80
depends_on:
– beesSpringboot
volumes:
– /bees/docker_volume/nginx/nginx.conf:/etc/nginx/nginx.conf
– /bees/docker_volume/nginx/conf.d:/etc/nginx/conf.d
– /bees/docker_volume/nginx/file:/home/file
image: 镜像名称 ports: 容器的端口和宿主机的端口的映射 services: 文中三个 service,在各自容器启动后就会自动生成别名,例如:在 springboot 中访问数据库,只需要通过“beesMysql:3306”就能访问。depends_on: 会设置被依赖的容器启动之后,才会启动自己。例如:mysql 数据库容器启动后,再启动 springboot 接口的容器。volumes:挂载卷,一些需要长久保存的文件,可通过宿主机中的目录,挂载到容器中,否则容器重启后会丢失。例如:数据库的数据文件;nginx 的配置文件和文件服务器目录。
其他
自动部署
为了提高开发效率,简单写了一个自动部署的脚本,直接贴脚本了:
#!/bin/bash

v_springboot_jar=`find /bees/devops/upload/ -name “*.jar”`
echo “ 找到 jar:”$v_springboot_jar
v_angular_zip=`find /bees/devops/upload/ -name “dist.zip”`
echo “ 找到 dist:”$v_angular_zip

cd /bees/conf/
docker-compose down
echo “ 关闭容器 ”

docker rmi -f $(docker images | grep “bees-springboot” | awk ‘{print $1}’)
docker rmi -f $(docker images | grep “bees-nginx-angular” | awk ‘{print $1}’)
echo “ 删除镜像 ”

cd /bees/devops/dockerfiles/springboot/
rm -f *.jar
cp $v_springboot_jar ./bees-0.0.1-SNAPSHOT.jar
docker build -t bees-springboot .
echo “ 生成 springboot 镜像 ”

cd /bees/devops/dockerfiles/angular/
rm -rf dist/
cp $v_angular_zip ./dist.zip
unzip dist.zip
rm -f dist.zip
docker build -t bees-nginx-angular .
echo “ 生成 angular 镜像 ”

cd /bees/conf/
docker-compose up -d
echo “ 启动容器 ”
docker ps
遇到的坑
一开始在 docker-compose.yml 文件中写 services 时,每个 service 不是驼峰式命名,而是下划线连接,例如:bees_springboot、bees_mysql、bees_nginx_angular。在 springboot 中访问数据库的别名可以,但是在 nginx 中,反向代理 springboot 接口地址时死活代理不了 bees_springboot 的别名。能在 bees_nginx_angular 的容器中 ping 通 bees_springboot,但是代理不了 bees_springboot 地址的接口,通过 curl -v 查看原因,是丢失了 host。最后发现,nginx 默认 request 的 header 中包含“_”下划线时,会自动忽略掉。我因此把 docker-compose.yml 中 service 名称,从下划线命名都改成了驼峰式。当然也可以通过在 nginx 里的 nginx.conf 配置文件中的 http 部分中添加如下配置解决:
underscores_in_headers on;

正文完
 0