一、前言

之前我们公司部署服务,就是大家都懂的那一套(安装JDK、Tomcat —> 编译好文件或者打war包上传 —> 启动Tomcat),这种部署方式一直持续了很久,带来的问题也很多:

1、繁重的发布任务。微服务一多,就要每个服务都要重启一遍,而且要是集群的话,那要启动的服务就更多了。

2、环境迁移报错。经常发生的一件事,同样的一套代码,这台服务器上就是能跑起来,换个服务器就是报错了。

3、士气低落。小公司没有正经的运维,都是让开发兼并着做这方面的工作,然后负责这块的同事怨言很多(因为这种发布部署实在太无趣了)。

所以领导决定引起 Docker 作为我们的部署方式,一来可以很好的解决目前项目部署存在的问题,二来为项目注入新鲜血液。

从上个月15号开始接触 Docker,到现在把我们系统的微服务架构初步搭建好,折腾了好久,踩了很多坑。纪念一下小成就,写了这篇博客。为了避免涉嫌泄露公司机密,就小而全的做一些简单介绍哈,以下面这张最小微服务架构图为例,部署一套 Dubbo 微服务。

二、服务镜像打包

1、Tomcat 基础环境搭建

我们系统的每个微服务都部署运行在 Tomcat 上(听说这种方式很不好,对于一些不是web工程的,没必要搭建成 web 服务,增加复杂性,也浪费系统资源),所以我的想法是:先搭建一套 Tomcat 环境镜像,然后每个微服务都基于这个环境镜像去构建。所以写了一个 tomcat-env 的镜像,思路如下:

-- 基于 JDK 的 Tomcat 容器(主要参考官网 Tomcat 镜像的 Dockerfile)。

-- 在上下文目录存放项目编译文件,并重命名为 ROOT(不放 war 包的原因是考虑调试的时候方便,不用改一个文件,就打个war包)。

-- 删除原本 Tomcat 容器 webapps 目录下的 ROOT 文件,并将上下文目录中项目的 ROOT 文件夹上传到容器 webapps 目录下。

-- 启动服务。

FROM openjdk:8-jre

ENV CATALINA_HOME /usr/local/tomcat
ENV PATH $CATALINA_HOME/bin:$PATH
RUN mkdir -p "$CATALINA_HOME"
WORKDIR $CATALINA_HOME # let "Tomcat Native" live somewhere isolated
ENV TOMCAT_NATIVE_LIBDIR $CATALINA_HOME/native-jni-lib
ENV LD_LIBRARY_PATH ${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOMCAT_NATIVE_LIBDIR # runtime dependencies for Tomcat Native Libraries
# Tomcat Native 1.2+ requires a newer version of OpenSSL than debian:jessie has available
# > checking OpenSSL library version >= 1.0.2...
# > configure: error: Your version of OpenSSL is not compatible with this version of tcnative
# see http://tomcat.10.x6.nabble.com/VOTE-Release-Apache-Tomcat-8-0-32-tp5046007p5046024.html (and following discussion)
# and https://github.com/docker-library/tomcat/pull/31
ENV OPENSSL_VERSION 1.1.0f-3+deb9u2
RUN set -ex; \
currentVersion="$(dpkg-query --show --showformat '${Version}\n' openssl)"; \
if dpkg --compare-versions "$currentVersion" '<<' "$OPENSSL_VERSION"; then \
if ! grep -q stretch /etc/apt/sources.list; then \
# only add stretch if we're not already building from within stretch
{ \
echo 'deb http://deb.debian.org/debian stretch main'; \
echo 'deb http://security.debian.org stretch/updates main'; \
echo 'deb http://deb.debian.org/debian stretch-updates main'; \
} > /etc/apt/sources.list.d/stretch.list; \
{ \
# add a negative "Pin-Priority" so that we never ever get packages from stretch unless we explicitly request them
echo 'Package: *'; \
echo 'Pin: release n=stretch*'; \
echo 'Pin-Priority: -10'; \
echo; \
# ... except OpenSSL, which is the reason we're here
echo 'Package: openssl libssl*'; \
echo "Pin: version $OPENSSL_VERSION"; \
echo 'Pin-Priority: 990'; \
} > /etc/apt/preferences.d/stretch-openssl; \
fi; \
apt-get update; \
apt-get install -y --no-install-recommends openssl="$OPENSSL_VERSION"; \
rm -rf /var/lib/apt/lists/*; \
fi RUN apt-get update && apt-get install -y --no-install-recommends \
libapr1 \
&& rm -rf /var/lib/apt/lists/* # see https://www.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/KEYS
# see also "update.sh" (https://github.com/docker-library/tomcat/blob/master/update.sh)
ENV GPG_KEYS 05AB33110949707C93A279E3D3EFE6B686867BA6 07E48665A34DCAFAE522E5E6266191C37C037D42 47309207D818FFD8DCD3F83F1931D684307A10A5 541FBE7D8F78B25E055DDEE13C370389288584E7 61B832AC2F1C5A90F0F9B00A1C506407564C17A3 713DA88BE50911535FE716F5208B0AB1D63011C7 79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED 9BA44C2621385CB966EBA586F72C284D731FABEE A27677289986DB50844682F8ACB77FC2E86E29AC A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23 ENV TOMCAT_MAJOR 8
ENV TOMCAT_VERSION 8.0.53
ENV TOMCAT_SHA512 cd8a4e48a629a2f2bb4ce6b101ebcce41da52b506064396ec1b2915c0b0d8d82123091242f2929a649bcd8b65ecf6cd1ab9c7d90ac0e261821097ab6fbe22df9 ENV TOMCAT_TGZ_URLS \
# https://issues.apache.org/jira/browse/INFRA-8753?focusedCommentId=14735394#comment-14735394
https://www.apache.org/dyn/closer.cgi?action=download&filename=tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz \
# if the version is outdated, we might have to pull from the dist/archive :/
https://www-us.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz \
https://www.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz \
https://archive.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz ENV TOMCAT_ASC_URLS \
https://www.apache.org/dyn/closer.cgi?action=download&filename=tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz.asc \
# not all the mirrors actually carry the .asc files :'(
https://www-us.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz.asc \
https://www.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz.asc \
https://archive.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz.asc RUN set -eux; \
\
savedAptMark="$(apt-mark showmanual)"; \
apt-get update; \
\
apt-get install -y --no-install-recommends gnupg dirmngr; \
\
export GNUPGHOME="$(mktemp -d)"; \
for key in $GPG_KEYS; do \
gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \
done; \
\
apt-get install -y --no-install-recommends wget ca-certificates; \
\
success=; \
for url in $TOMCAT_TGZ_URLS; do \
if wget -O tomcat.tar.gz "$url"; then \
success=1; \
break; \
fi; \
done; \
[ -n "$success" ]; \
\
echo "$TOMCAT_SHA512 *tomcat.tar.gz" | sha512sum -c -; \
\
success=; \
for url in $TOMCAT_ASC_URLS; do \
if wget -O tomcat.tar.gz.asc "$url"; then \
success=1; \
break; \
fi; \
done; \
[ -n "$success" ]; \
\
gpg --batch --verify tomcat.tar.gz.asc tomcat.tar.gz; \
tar -xvf tomcat.tar.gz --strip-components=1; \
rm bin/*.bat; \
rm tomcat.tar.gz*; \
command -v gpgconf && gpgconf --kill all || :; \
rm -rf "$GNUPGHOME"; \
\
nativeBuildDir="$(mktemp -d)"; \
tar -xvf bin/tomcat-native.tar.gz -C "$nativeBuildDir" --strip-components=1; \
apt-get install -y --no-install-recommends \
dpkg-dev \
gcc \
libapr1-dev \
libssl-dev \
make \
"openjdk-${JAVA_VERSION%%[.~bu-]*}-jdk=$JAVA_DEBIAN_VERSION" \
; \
( \
export CATALINA_HOME="$PWD"; \
cd "$nativeBuildDir/native"; \
gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \
./configure \
--build="$gnuArch" \
--libdir="$TOMCAT_NATIVE_LIBDIR" \
--prefix="$CATALINA_HOME" \
--with-apr="$(which apr-1-config)" \
--with-java-home="$(docker-java-home)" \
--with-ssl=yes; \
make -j "$(nproc)"; \
make install; \
); \
rm -rf "$nativeBuildDir"; \
rm bin/tomcat-native.tar.gz; \
\
# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies
apt-mark auto '.*' > /dev/null; \
[ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
rm -rf /var/lib/apt/lists/*; \
\
# sh removes env vars it doesn't support (ones with periods)
# https://github.com/docker-library/tomcat/issues/77
find ./bin/ -name '*.sh' -exec sed -ri 's|^#!/bin/sh$|#!/usr/bin/env bash|' '{}' + # verify Tomcat Native is working properly
RUN set -e \
&& nativeLines="$(catalina.sh configtest 2>&1)" \
&& nativeLines="$(echo "$nativeLines" | grep 'Apache Tomcat Native')" \
&& nativeLines="$(echo "$nativeLines" | sort -u)" \
&& if ! echo "$nativeLines" | grep 'INFO: Loaded APR based Apache Tomcat Native library' >&2; then \
echo >&2 "$nativeLines"; \
exit 1; \
fi EXPOSE 8080
RUN rm -rf /usr/local/tomcat/webapps/ROOT/
ONBUILD COPY ROOT /usr/local/tomcat/webapps/ROOT/
ONBUILD ENTRYPOINT ["/usr/local/tomcat/bin/catalina.sh","run"]

tomcat-env

看起来很复杂,不要被吓到,其实都是抄的官网 Tomcat 镜像的Dockerfile,然后改动了一点,主要是后面三句:删除容器 ROOT 文件夹,拷贝上下文目录的 ROOT 文件夹到 wenapps 目录下,重启服务。

RUN rm -rf /usr/local/tomcat/webapps/ROOT/
ONBUILD COPY ROOT /usr/local/tomcat/webapps/ROOT/
ONBUILD ENTRYPOINT ["/usr/local/tomcat/bin/catalina.sh","run"]

tips:1、ONBUILD 命令本次镜像不会被执行,只有以这个镜像为基础镜像的时候才会被执行。

2、上下文目录指的是 Dockerfile 文件所在的目录。

3、该镜像已上传到 DockerHub 上:https://hub.docker.com/r/jmcui/tomcat-env/

2、微服务镜像打包

有了基础环境镜像 tomcat-env,那么打包一个服务镜像就是一件再简单不过的事情了:

FROM tomcat-env:1.0

没错,就是这么简单,因为我们把所有的工作都放在 tomcat-env 中了,其实就是那个 ONBUILD 命令的效果啦~~

三、编排文件 docker-compose.yml

微服务项目要部署起来,主要是靠 docker-compose.yml 文件进行编排,规定服务之间的关联以及先后启动顺序,然后把几十个零散的微服务当成一个整体来统一管理。

首先,困扰我的是网络问题。做过开发的都知道,要在项目中指定(Spring 在 applicationContext.xml)数据库地址和 Zookeeper 地址,那么我怎么知道容器的 ip 地址是多少呢?先来了解下 Docker 的网络模式?

Docker 的默认网络配置是 "bridge",当 Docker 启动时,会自动在主机上创建一个 docker0 虚拟网桥,实际上是 Linux 的一个 bridge,可以理解为一个软件交换机。Docker 会随机分配一个本地未占用的私有网段(在 RFC1918 中定义)中的一个地址给 docker0 接口,它会在挂载到它的网口之间进行转发。当创建一个 Docker 容器的时候,同时会创建了一对 veth pair 接口。这对接口一端在容器内,即 eth0;另一端在本地并被挂载到 docker0 网桥,名称以 veth 开头(例如 vethAQI2QT)。通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。

也就是说,每次容器启动以后的 ip 地址是不固定的,这该怎么办呢?当然可以写死 IP 地址,规定局域网网段,给每个服务编排 IP 地址;当然也可以把network_mode="host",统一用宿主机的网络地址。当然!这些都不是最好的办法:

version: '3.7'
#服务列表
services:
#基础组件 zookeeper
zookeeper:
image: zookeeper
restart: always
ports:
- 4181:2181
#基础组件 MySQL
db:
image: mysql:5.7.17
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --init-connect='SET NAMES utf8mb4;'
ports:
- "3636:3306"
volumes:
- /var/mysqldb:/var/lib/mysql
- /docker/mysql/my.cnf:/etc/mysql/mysql.conf.d/mysqld.cnf
restart: always
environment:
MYSQL_ROOT_PASSWORD: password
#消费者服务1 admin
admin:
image: "admin:2.3.1"
ports:
- "7575:8080"
depends_on:
- zookeeper
restart: always
environment:
zookeeper.host: zookeeper://zookeeper:2181
#提供者服务1 system
system:
image: "system:2.3.1"
depends_on:
- db
- zookeeper
restart: always
environment:
zookeeper.host: zookeeper://zookeeper:2181
mysql.address: db:3306

看到了吗?IP 地址直接由 服务名 指定就可以了。另外, Docker 中设置的环境变量,竟然能被 applicationContext.xml 中读取,我也是蛮诧异的!(在代码和 Docker 中都配置了mysql.address 的话,以 Docker 中设置的生效)。

然后 docker-compose up -d 启动微服务项目就可以了~~

容器部署的一个原则:尽量不要在容器内部做文件的修改,要修改的内容用数据卷的方式映射到宿主机上,比如上面的MySQL配置文件和数据仓库。

在 Docker 上部署 MySQL 遇到了几个问题,简单罗列下:

1、Navicat 连接的时候: Client does not support authentication protocol requested by server ?

解决:进入 MySQL 容器,运行

ALTER user 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'password';

2、Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggre 的问题?

原因:MySQL 5.7.5及以上功能依赖检测功能。如果启用了ONLY_FULL_GROUP_BY SQL模式(默认情况下),MySQL将拒绝选择列表,HAVING条件或ORDER BY列表的查询引用在GROUP BY子句中既未命名的非集合列,也不在功能上依赖于它们。

解决:在MySQL的配置文件中加上:

sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION

3、MySQL 连接参数useSSL=true 和 useSSL=false 的区别?

建议不要在没有服务器身份验证的情况下建立SSL连接(同一个 Docker-compose 中是内网环境)。根据 MySQL 5.5.45 +,5.6.26 +和5.7.6+ 要求如果未设置显式选项,则必须默认建立SSL连接。为了符合不使用SSL的现有应用程序。您需要通过设置useSSL = false显式禁用SSL,或者设置useSSL = true并为服务器证书验证提供信任库。

四、结语

总算是把一个微服务项目部署运行起来了,几乎是用了最少的 Docker-compose 模板文件,所以还是有很多地方可以完善的,比如说 MySQL 密码没有加密处理、服务没有做健康检查、集群方面还没怎么考虑(用 Docker Swarm 实现)等等......路漫漫其修远兮,吾将上下而求索。共勉!

Docker 系列七(Dubbo 微服务部署实践).的更多相关文章

  1. docker微服务部署之:七、Rancher进行微服务扩容和缩容

    docker微服务部署之:六.Rancher管理部署微服务 Rancher有两个特色用起来很方便,那就是扩容和缩容. 一.扩容前的准备工作 为了能直观的查看效果,需要修改下demo_article项目 ...

  2. 基于 Docker 的微服务架构实践

    本文来自作者 未闻 在 GitChat 分享的{基于 Docker 的微服务架构实践} 前言 基于 Docker 的容器技术是在2015年的时候开始接触的,两年多的时间,作为一名 Docker 的 D ...

  3. 微服务架构实践 - 你只懂docker与spring boot就够了吗?

    微服务架构实践 - 你只懂docker与spring boot就够了吗? 作者 浮云发发 已关注 2017.02.27 02:50* 字数 2613 阅读 2583评论 6喜欢 35赞赏 2 微服务并 ...

  4. docker微服务部署之:六、Rancher管理部署微服务

    docker微服务部署之:五.利用DockerMaven插件自动构建镜像 一. 什么是Rancher Rancher是一个开源的企业级容器管理平台.通过Rancher,企业再也不必自己使用一系列的开源 ...

  5. QCon技术干货:个推基于Docker和Kubernetes的微服务实践

    2016年伊始,Docker无比兴盛,如今Kubernetes万人瞩目.在这个无比需要创新与速度的时代,由容器.微服务.DevOps构成的云原生席卷整个IT界.在近期举办的QCon全球软件开发大会上, ...

  6. 8.实战交付一套dubbo微服务到k8s集群(1)之Zookeeper部署

    1.基础架构 主机名 角色 ip HDSS7-11.host.com K8S代理节点1,zk1 10.4.7.11 HDSS7-12.host.com K8S代理节点2,zk2 10.4.7.12 H ...

  7. 将微服务部署到 Azure Kubernetes 服务 (AKS) 实践

    本文是对 <.NET Tutorial - Deploy a microservice to Azure> 的翻译和实践.入门级踩坑实践,k8s 大佬请回避,以免耽误您宝贵的时间. 介绍 ...

  8. docker微服务部署之:五、利用DockerMaven插件自动构建镜像

    docker微服务部署之:四.安装docker.docker中安装mysql和jdk1.8.手动构建镜像.部署项目 在上一篇文章中,我们是手动构建镜像,即: 4.1.2.5.1.2.6.1.2中的将d ...

  9. docker微服务部署之:四、安装docker、docker中安装mysql和jdk1.8、手动构建镜像、部署项目

    docker微服务部署之:三,搭建Zuul微服务项目 1.Centos7安装Docker 详见:Centos7安装Docker 2.Docker中安装jdk1.8 详见:使用Docker构建jdk1. ...

随机推荐

  1. Android使用ViewPager+PhotoView实现图片查看器

    可实现功能效果说明: 可实现多张图片点击放大,手指控制,左右滑动,多张图片点击任意位置定位显示任意位置图片:无动画,可自己加 效果图:                             核心代码 ...

  2. 【sql注入】简单实现二次注入

    [sql注入]简单实现二次注入 本文转自:i春秋社区 测试代码1:内容详情页面 [PHP] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 1 ...

  3. 谈谈Java中的代理模式

    首先来看一下代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用, 其特征是代理类与 ...

  4. 第83节:Java中的学生管理系统分页功能

    第83节:Java中的学生管理系统分页功能 分页功能一般可以做成两种,一种是物理分页,另一种是逻辑分页.这两种功能是有各自的特点的,物理分页是查询的时候,对数据库进行访问,只是查一页数据就进行返回,其 ...

  5. Android精通之AsyncTask与ListView讲解

    版权声明:未经博主允许不得转载 AsyncTask 了解AsyncTask异步,需要了解一下异步任务(多线程),什么是线程,可以这么说线程好比边吃饭边看电视,AsyncTask是为了方便后台线程中操作 ...

  6. Python - 一些值得阅读的PEP

    1- PEP简介 PEP是Python增强提案(Python Enhancement Proposal)的缩写.社区通过PEP来给Python语言建言献策,每个版本的新特性和变化都是通过PEP提案经过 ...

  7. Redis 常用操作命令,非常详细!

    下面总结并演示了 Redis 的 常用管理命令.key 操作.字符串.集合.列表.散列类型的操作命令. 你需要掌握的 Redis 知识 史上最全 Redis 高可用解决方案总结 为什么分布式一定要有R ...

  8. Spring面试底层原理的那些问题,你是不是真的懂Spring?

    1.什么是 Spring 框架?Spring 框架有哪些主要模块?Spring 框架是一个为 Java 应用程序的开发提供了综合.广泛的基础性支持的 Java 平台.Spring帮助开发者解决了开发中 ...

  9. MySQL(3)---MySQL优化

    MySQL优化 一.单表.双表.三表优化 1.单表    首先结论就是,range类型查询字段后面的索引全都无效 (1)建表 create table if not exists article( i ...

  10. MySQL 分支的选择:Percona 还是 MariaDB

    原文:https://www.biaodianfu.com/mysql-percona-or-mariadb.html 在MySQL被Oracle收购以后,越来越多的人对于MySQL的前景表示了担忧, ...