本文是《Docker下,两分钟极速体验Nacos》的续篇,前文我们极速体验了Nacos注册中心、服务提供者、服务消费者,这些应用都对应着不同的Docker容器,今天就来细说这些Docker容器的镜像。
回顾上一章的业务流程
先来回顾一下上一章,整个Docker环境中有哪些容器,提供了什么服务,如下图,请顺着橙色提示框的数字顺序来看请整个流程::
Nacos环境背后对应的Docker技术
在Docker下搭建一个包含Nacos注册中心、服务提供者、服务消费者的环境,总的来说需要做下面这些事情:
- 制作Nacos镜像;
- 制作服务提供者镜像;
- 制作服务消费者镜像;
- 制作docker-compose.yml文件,将容器编排在一起,然后一次性启动;
接下来我们逐个开发上面提到的内容;
源码下载
如果您不打算写代码,也可以从GitHub上下载本次实战的源码,地址和链接信息如下表所示:
名称 | 链接 | 备注 |
项目主页 | 该项目在GitHub上的主页 | |
git仓库地址(https) | 该项目源码的仓库地址,https协议 | |
git仓库地址(ssh) | git@github.com:zq2599/blog_demos.git | 该项目源码的仓库地址,ssh协议 |
这个git项目中有多个文件夹,本章的应用在nacosdemo文件夹下,如下图所示:
制作Nacos镜像
对Nacos镜像的功能要求有以下三点:
- 包含Nacos server应用;
- 暴露web管理服务的端口;
- 容器启动时,Nacos服务能够自动启动;
为了满足以上要求,除了编写Dockerfile文件,还要编写docker-entrypoint.sh文件,在容器创建时执行该文件用于启动Nacos服务;
首先是Dockerfile文件,该文件用于制作Nacos镜像:
# Docker image for Anaconda3-2019.03
# VERSION 0.0.1
# Author: bolingcavalry
### 基础镜像,使用alpine操作系统,openjkd使用8u201
FROM openjdk:8u201-jdk-alpine3.9
#作者
MAINTAINER BolingCavalry <zq2599@gmail.com>
#系统编码
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
#path
ENV PATH /opt/conda/bin:$PATH
#安装必要的软件
#RUN apt-get update --fix-missing && apt-get install -y wget
RUN apk update && apk add wget
#下载下来的压缩文件名称
ENV NACOS_FILE_NAME nacos-server-1.1.0.tar.gz
#把启动时用到的文件准备好
COPY ./docker-entrypoint.sh /docker-entrypoint.sh
#解压后的文件夹名称
ENV NACOS_FOLDER_NAME nacos
RUN wget https://github.com/alibaba/nacos/releases/download/1.1.0/nacos-server-1.1.0.tar.gz -O ~/$NACOS_FILE_NAME && \
tar -zxf ~/$NACOS_FILE_NAME -C ~/ && \
rm ~/$NACOS_FILE_NAME && \
chmod a+x /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]
EXPOSE 8848
从Dockerfile内容中可见,先去nacos的github下载安装包,然后解压,再复制docker-entrypoint.sh到镜像中,最后将nacos的8848端口暴露出来;
再来看看docker-entrypoint.sh文件的内容,该文件在容器启动时会被执行,内容很简单,就是进入nacos的bin目录,执行启动文件,再将start.out输出到控制台:
#!/bin/sh
echo "Starting nacos"n && \
cd ~/nacos/bin && \
./startup.sh -m standalone && \
cd ../logs && \
tail -f start.out
有两个重要信息需要注意:
- nacos的官方参考启动命令是./startup.sh -m standalone,这个命令会将jvm的输出重定向到start.out文件,也就是说nacos的JVM进程是在后台运行的,不会占用控制台(相比之下,spring boot应用使用java -jar启动时会占用控制台),对于docker来说,容器内的进程如果不占用控制台,docker就认为该容器已经结束工作,就会停止该容器,所以,为了避免nacos在docker刚刚启动就退出,需要用tail -f start.out来占领控制台;
- 用tail -f start.out来占领控制台可以避免容器刚刚启动就退出,但也有个弊端,就是容器中有了多个进程,并且nacos进程的PID不是1,所以在执行docker stop命令时,结束进程的信号量不会到nacos进程,而是去了PID等于1的进程,所以nacos进程不会立即退出,只能等到30秒后被强制kill,这个问题最好的解法是修改nacos的startup.sh,让nacos进程始终保持在控制台,不要重定向到后台,但这样就导致Dockerfile不好处理了,每次下载和解压了nacos安装包后,都要用本地的startup.sh去替换原有的,这样做的话,如果nacos升级版本,这边本地的startup.sh也要随之更新,很是麻烦…
构建镜像
- Dockerfile和docker-entrypoint.sh文件准备好之后放在同一个目录,执行以下命令构建镜像:
docker build -t bolingcavalry/nacossimpleprovider:1.0-SNAPSHOT
- 如果您在hub.docker.com已经注册,可以执行以下命令将本地镜像上传到 hub.docker.com ,这样任何人都可以下载使用该镜像了:
docker push bolingcavalry/nacossimpleprovider:1.0-SNAPSHOT
Nacos镜像的制作已经完成,接下来制作一个java应用的镜像:服务提供者;
java应用的父工程
接下来要开发的simple-provider和simple-consumer两个应用都是java应用,为了管理方便,做一个基于maven的父工程,再将simple-provider和simple-consumer以module的形式加入到这个父工程中;
- 基于maven创建父工程,名为nacosdemo,其pom.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<modules>
<module>simpleconsumer</module>
<module>simpleprovider</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.bolingcavalry</groupId>
<artifactId>nacosdemo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring.boot.version>2.0.5.RELEASE</spring.boot.version>
<spring.cloud.version>Finchley.SR1</spring.cloud.version>
<spring.cloud.alibaba.version>0.2.2.RELEASE</spring.cloud.alibaba.version>
</properties>
<!-- 依赖声明 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--编译管理 jdk版本和字符集编码-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
可见这是个普通的父工程,里面对spring cloud和spring cloud alibaba的版本做了控制,避免子工程还要各种指定版本的繁琐操作;
制作服务提供者镜像
simple-provider是个java web应用,使用了spring cloud alibaba的依赖库之后可以使用Nacos的注册发现服务,整个工程的开发步骤如下:
- 基于maven创建工程,其pom.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>nacosdemo</artifactId>
<groupId>com.bolingcavalry</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>simpleprovider</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!--使用jib插件-->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>1.3.0</version>
<configuration>
<!--from节点用来设置镜像的基础镜像,相当于Docerkfile中的FROM关键字-->
<from>
<!--使用openjdk官方镜像,tag是8u201-jdk-alpine3.9,表示镜像的操作系统是alpine3.9,装好了jdk8u201-->
<image>openjdk:8u201-jdk-alpine3.9</image>
</from>
<to>
<!--镜像名称和tag,使用了mvn内置变量${project.artifactId}和${project.version}-->
<image>bolingcavalry/nacos${project.artifactId}:${project.version}</image>
</to>
<!--容器相关的属性-->
<container>
<!--jvm内存参数-->
<jvmFlags>
<jvmFlag>-Xms1g</jvmFlag>
<jvmFlag>-Xmx1g</jvmFlag>
</jvmFlags>
</container>
</configuration>
</plugin>
</plugins>
</build>
</project>
上述内容有两点需要注意:
a. 依赖spring-cloud-starter-alibaba-nacos-discovery,这样可以用上spring cloud nacos的服务;
b. 使用了maven插件jib-maven-plugin,用于将应用构建成docker镜像,此插件相关的详情请参考《Docker与Jib(maven插件版)实战》;
2. 配置文件application.properties,配置应用名称和nacos地址,注意这里nacos地址配置的是nacoshost,对应的是后面docker-compose.yml中的link参数:
spring.application.name=simple-provider
spring.cloud.nacos.discovery.server-addr=nacoshost:8848
- 应用启动类SimpleProviderApplication ,配置了注解EnableDiscoveryClient,用于启动注册发现服务:
package simpleprovider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class SimpleProviderApplication {
public static void main(String[] args) {
SpringApplication.run(SimpleProviderApplication.class, args);
}
}
- 增加一个提供http服务的controller类ProviderController:
package simpleprovider.controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @Description: 提供web服务的controller
* @author: willzhao E-mail: zq2599@gmail.com
* @date: 2019/7/28 11:08
*/
@RestController
public class ProviderController {
@RequestMapping(value = "/hello/{name}", method = RequestMethod.GET)
public String hello(@PathVariable("name") String name){
return "hello " + name + ", " + new SimpleDateFormat("yyyy-mm-dd HH:mm:ss").format(new Date());
}
}
- 以上就是simple-provider的所有源码了,在pom.xml所在目录执行以下命令,即可构建docker镜像,存入本地仓库:
mvn compile jib:dockerBuild
制作服务消费者镜像
simple-consumer是个java web应用,启动后对外提供http服务,响应的时候,通过nacos取得simple-provider的地址,然后向simple-provider发请求,将响应返回给浏览器:
- 基于maven创建工程,其pom.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>nacosdemo</artifactId>
<groupId>com.bolingcavalry</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>simpleconsumer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!--使用jib插件-->
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>1.3.0</version>
<configuration>
<!--from节点用来设置镜像的基础镜像,相当于Docerkfile中的FROM关键字-->
<from>
<!--使用openjdk官方镜像,tag是8u201-jdk-alpine3.9,表示镜像的操作系统是alpine3.9,装好了jdk8u201-->
<image>openjdk:8u201-jdk-alpine3.9</image>
</from>
<to>
<!--镜像名称和tag,使用了mvn内置变量${project.artifactId}和${project.version}-->
<image>bolingcavalry/nacos${project.artifactId}:${project.version}</image>
</to>
<!--容器相关的属性-->
<container>
<!--jvm内存参数-->
<jvmFlags>
<jvmFlag>-Xms1g</jvmFlag>
<jvmFlag>-Xmx1g</jvmFlag>
</jvmFlags>
<!--要暴露的端口-->
<ports>
<port>8080</port>
</ports>
</container>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 配置文件application.properties,配置应用名称和nacos地址,注意这里nacos地址配置的是nacoshost,对应的是后面docker-compose.yml中的link参数:
spring.application.name=simple-consumer
spring.cloud.nacos.discovery.server-addr=nacoshost:8848
- 应用启动类SimpleConsumerApplication,配置了注解EnableDiscoveryClient,用于启动注册发现服务:
package com.bolingcavalry.simpleconsumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class SimpleConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SimpleConsumerApplication.class, args);
}
}
- 增加一个提供http服务的controller类ConsumerController,通过LoadBalancerClient取得simple-provider的服务地址,然后发请求过去 :
package com.bolingcavalry.simpleconsumer.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @Description: 提供web服务的controller
* @author: willzhao E-mail: zq2599@gmail.com
* @date: 2019/7/28 11:08
*/
@RestController
public class ConsumerController {
@Autowired
LoadBalancerClient loadBalancerClient;
@RequestMapping("/test")
public String test(){
//根据应用名称取得实例对象
ServiceInstance serviceInstance = loadBalancerClient.choose("simple-provider");
//根据实例对象取得地址
String uri = serviceInstance.getUri().toString();
String result = new RestTemplate().getForObject(uri + "/hello/bolingcavalry", String.class);
return "provider uri : " + uri + "<br>" + "response :" + result;
}
}
- 以上就是simple-consumer的所有源码了,在pom.xml所在目录执行以下命令,即可构建docker镜像,存入本地仓库:
mvn compile jib:dockerBuild
编写docker-compose.yml
三个镜像都准备好了,接下来是做容器编排,docker-compose.yml内容如下:
version: '2'
services:
nacos:
image: bolingcavalry/nacosserver:0.0.1
container_name: nacos
restart: unless-stopped
ports:
- '8848:8848'
provider:
image: bolingcavalry/nacossimpleprovider:1.0-SNAPSHOT
links:
- nacos:nacoshost
depends_on:
- nacos
restart: unless-stopped
consumer:
image: bolingcavalry/nacossimpleconsumer:1.0-SNAPSHOT
links:
- nacos:nacoshost
container_name: consumer
depends_on:
- nacos
ports:
- '8080:8080'
restart: unless-stopped
上述编排文件,有几点需要注意:
a. 可以用container_name设置容器的名字,但是不要给provider的容器设置名字,因为按照规划provider会启动多个容器,指定名字会导致第二个容器启动时报名字冲突的错误;
b. provider和consumer通过depends_on参数,将自己的启动时间放在了nacos后面;
c. links的作用是将nacos的IP地址,写入provider和consumer的/etc/hosts文件中,这样这两个容器内容的应用可以通过nacoshost来访问nacos了;
启动多个容器
在docker-compose.yml所在目录执行以下命令,即可启动所有容器,并且provider容器会启动6个:
docker-compose up --scale provider=6 -d
至此,整个nacos的docker环境搭建过程已经回顾完毕,在您搭建自己的容器环境时,希望本文能给您一些参考;