以前咱們公司部署服務,就是你們都懂的那一套(安裝JDK、Tomcat —> 編譯好文件或者打war包上傳 —> 啓動Tomcat),這種部署方式一直持續了好久,帶來的問題也不少:html
一、繁重的發佈任務。微服務一多,就要每一個服務都要重啓一遍,並且要是集羣的話,那要啓動的服務就更多了。java
二、環境遷移報錯。常常發生的一件事,一樣的一套代碼,這臺服務器上就是能跑起來,換個服務器就是報錯了。mysql
三、士氣低落。小公司沒有正經的運維,都是讓開發兼併着作這方面的工做,而後負責這塊的同事怨言不少(由於這種發佈部署實在太無趣了)。git
因此領導決定引發 Docker 做爲咱們的部署方式,一來能夠很好的解決目前項目部署存在的問題,二來爲項目注入新鮮血液。github
從上個月15號開始接觸 Docker,到如今把咱們系統的微服務架構初步搭建好,折騰了很久,踩了不少坑。記念一下小成就,寫了這篇博客。爲了不涉嫌泄露公司機密,就小而全的作一些簡單介紹哈,如下面這張最小微服務架構圖爲例,部署一套 Dubbo 微服務。web
咱們系統的每一個微服務都部署運行在 Tomcat 上(據說這種方式很很差,對於一些不是web工程的,不必搭建成 web 服務,增長複雜性,也浪費系統資源),因此個人想法是:先搭建一套 Tomcat 環境鏡像,而後每一個微服務都基於這個環境鏡像去構建。因此寫了一個 tomcat-env 的鏡像,思路以下:sql
-- 基於 JDK 的 Tomcat 容器(主要參考官網 Tomcat 鏡像的 Dockerfile)。docker
-- 在上下文目錄存放項目編譯文件,並重命名爲 ROOT(不放 war 包的緣由是考慮調試的時候方便,不用改一個文件,就打個war包)。數據庫
-- 刪除本來 Tomcat 容器 webapps 目錄下的 ROOT 文件,並將上下文目錄中項目的 ROOT 文件夾上傳到容器 webapps 目錄下。apache
-- 啓動服務。
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 鏡像的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:一、ONBUILD 命令本次鏡像不會被執行,只有以這個鏡像爲基礎鏡像的時候纔會被執行。
二、上下文目錄指的是 Dockerfile 文件所在的目錄。
三、該鏡像已上傳到 DockerHub 上:https://hub.docker.com/r/jmcui/tomcat-env/
有了基礎環境鏡像 tomcat-env,那麼打包一個服務鏡像就是一件再簡單不過的事情了:
FROM tomcat-env:1.0
沒錯,就是這麼簡單,由於咱們把全部的工做都放在 tomcat-env 中了,其實就是那個 ONBUILD 命令的效果啦~~
微服務項目要部署起來,主要是靠 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 遇到了幾個問題,簡單羅列下:
一、Navicat 鏈接的時候: Client does not support authentication protocol requested by server ?
解決:進入 MySQL 容器,運行
ALTER user 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
二、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
三、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 實現)等等......路漫漫其修遠兮,吾將上下而求索。共勉!