在软件开发技术更新迭代的历史以来,学习一门技术最有上手意义的莫过于来一个helloworld的例子,那么我们学习docker也遵循这种方式。

前面我们通过虚拟化软件已经从windows系统中虚拟出3个节点作为学习docker服务器,接着我们又经历了私服的漫长搭建过程,接下来我们将进入实战环节。

首先我们需要创建一个java springboot web应用,将其打包后上传到节点1,通过docker push命令推送镜像到私服,然后再编写Dockerfile脚本将应用构建出一个镜像,再通过镜像创建和运行docker容器。

通过helloworld实战后,相信读者可以快速体验docker部署独立容器的整个流程和方式。

接下来我们将通过创建一个简单的Java Spring Boot Web应用,将其部署到docker容器中,下面是项目结构:

 

Docker实战篇-快速开始_Docker

其中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>   <parent>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-parent</artifactId>      <version>2.1.3.RELEASE</version>      <relativePath/> <!-- lookup parent from repository -->   </parent>   <groupId>com.lazy.study.docker</groupId>   <artifactId>lazy-study-docker</artifactId>   <version>0.0.1-SNAPSHOT</version>   <name>lazy-study-docker</name>   <description>Demo project for Spring Boot</description>   <properties>      <java.version>1.8</java.version>   </properties>   <dependencies>      <dependency>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-data-redis</artifactId>      </dependency>      <dependency>         <groupId>org.apache.commons</groupId>         <artifactId>commons-pool2</artifactId>      </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>

HelloController 类内容如下:

  •  
package com.lazy.study.docker;
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;
/** * <p></p> * * @author laizhiyuan * @since 2019/3/26. */@RestControllerpublic class HelloController { @Autowired private RedisTemplate<String, Object> redisTemplate;
@GetMapping("/visitor") public String visitor() { String result = "Hello World "; try { Long visitorCount = redisTemplate.opsForValue().increment("visitorCount");
result += "你是第[" + visitorCount + "]个访客"; } catch (Exception ex) { result += "connection to redis error"; } return result; }}

 

 

RedisAutoConfiguration类内容如下:

 

  •  
package com.lazy.study.docker;
import com.fasterxml.jackson.annotation.JsonAutoDetect;import com.fasterxml.jackson.annotation.PropertyAccessor;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.cache.annotation.CachingConfigurerSupport;import org.springframework.cache.annotation.EnableCaching;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.RedisSerializer;
import java.net.UnknownHostException;
/** * <p> * Redis Cache Config Java Class * redis命令手册可参考:http://doc.redisfans.com/ * </p> * * @author laizhiyuan * @date 2018/12/27. */@EnableCaching@Configuration@SuppressWarnings("unchecked")public class RedisAutoConfiguration extends CachingConfigurerSupport {
@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); // 使用Jackson2JsonRedisSerialize 替换默认序列化 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 设置value的序列化规则和 key的序列化规则 redisTemplate.setValueSerializer(RedisSerializer.json()); redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string()); redisTemplate.setHashValueSerializer(RedisSerializer.json());
redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet();
return redisTemplate; }}

 

LazyStudyDockerApplication 类内容如下:

  •  
package com.lazy.study.docker;
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;
/** * @author laizhiyuan */@SpringBootApplicationpublic class LazyStudyDockerApplication {
public static void main(String[] args) { SpringApplication.run(LazyStudyDockerApplication.class, args); }}

 

logback.xml内容如下:

  •  
<?xml version="1.0" encoding="UTF-8"?><!-- Logback configuration. See http://logback.qos.ch/manual/index.html --><configuration scan="true" scanPeriod="10 seconds">
<include resource="org/springframework/boot/logging/logback/base.xml" /> <property name="LOG_PATH" value="/opt/applications/helloworld/logs" /> <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${LOG_PATH}/info.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_PATH}/info-%d{yyyyMMdd}.log.%i</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>500MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <maxHistory>2</maxHistory> </rollingPolicy> <layout class="ch.qos.logback.classic.PatternLayout"> <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n</Pattern> </layout> </appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> </filter> <File>${LOG_PATH}/error.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_PATH}/error-%d{yyyyMMdd}.log.%i </fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>500MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <maxHistory>2</maxHistory> </rollingPolicy> <layout class="ch.qos.logback.classic.PatternLayout"> <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n</Pattern> </layout> </appender>
<!-- hibernate日志配置 --> <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="ERROR" /> <logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="ERROR" /> <logger name="org.hibernate.SQL" level="ERROR" /> <logger name="org.hibernate.engine.QueryParameters" level="ERROR" /> <logger name="org.hibernate.engine.query.HQLQueryPlan" level="ERROR" />
<root level="INFO"> <appender-ref ref="INFO_FILE" /> <appender-ref ref="ERROR_FILE" /> </root>
</configuration>

 

 

注意日志保存的路径为:/opt/applications/helloworld/logs后面我们需要把这个路径绑定到宿主机。

application.properties内容如下:

  •  
server.port=8100
# Redis数据库索引(默认为0)spring.redis.database=0# Redis服务器地址spring.redis.host=myredis# Redis服务器连接端口spring.redis.port=6379# Redis服务器连接密码(默认为空)spring.redis.password=# 连接池最大连接数(使用负值表示没有限制)spring.redis.lettuce.pool.max-active=8# 连接池最大阻塞等待时间(使用负值表示没有限制)spring.redis.lettuce.pool.max-idle=-1# 连接池中的最大空闲连接spring.redis.lettuce.pool.max-wait=8s# 连接池中的最小空闲连接spring.redis.lettuce.pool.min-idle=0# 连接超时时间(毫秒)spring.redis.timeout=6000ms# 缓存类型spring.cache.type=redis
logging.config=classpath:logback.xmldebug=truelogging.level.org.springframework.web=INFOlogging.level.org.hibernate=INFOspring.output.ansi.enabled=DETECT

 

 

以上我们罗列一堆代码和配置,应用功能非常简答,就是每当有人访问该应用程序是,程序通过连接redis,原子累计加1后返回上一个值告诉客户端属于第几个访客。为了测试应用,我们启动它,访问:http://127.0.0.1:8100/visitor 结果如下:

 

因为我们redis并没有部署,所以连接失败,这个我们先不管它,我们目的是知道如何将一个应用部署为docker容器。

 

接下来没有特别声明的情况下,所有的命令均在node1虚拟机上执行。

1、创建目录mkdir -p /usr/local/docker/build/myhelloworld

2、将lazy-study-docker打包上传到/usr/local/docker/build/myhelloworld

笔者打包名称为:

lazy-study-docker-0.0.1-SNAPSHOT.jar

3、复制一份jdk到目录/usr/local/docker/build/myhelloworld下

4、在目录/usr/local/docker/build/myhelloworld下编写Dockerfile_helloworld文件,内容如下:

  •  
FROM registry.laizhiy.cn/ubuntu:16.04 RUN mkdir -p /opt/applications/helloworld &&\    mkdir -p /opt/software COPY jdk1.8.0_161 /opt/software/jdk1.8.0_161 COPY lazy-study-docker-0.0.1-SNAPSHOT.jar /opt/applications/helloworld/ ENV JAVA_HOME /opt/software/jdk1.8.0_161ENV PATH JAVA_HOME/bin:$PATH CMD java -jar /opt/applications/helloworld/lazy-study-docker-0.0.1-SNAPSHOT.jar

 

注意FROM后面的基础镜像[ registry.laizhiy.cn/ubuntu:16.04],我们开始使用前面我们在节点2上搭建的私服,这也符合实际操作。目前我们构建上下文目录内容如下:

 

Docker实战篇-快速开始_Docker_02

整体操作步骤如下:

1、在私服节点1登录私服:

 

  •  
docker login registry.laizhiy.cndocker build -t myhelloworld -f Dockerfile_helloworld .

 

注意:最后面有个点,,代表当前目录所有文件参与构建上下文。运行容器:

 

  •  
docker run -d --rm \ -v /var/myhelloworld/logs:/opt/applications/helloworld/logs \-p 8100:8100 \--name myhelloworld \myhelloworld:latest

 

 

停掉宿主机防火墙:

  •  
systemctl stop firewalld

 

浏览器访问:http://node1:8100/visitor

 

现在myredis还没相关的服务到时笔者打算将myredis也以容器的方式部署,我们会发现,我们应用一般都会使用很多组件或相关服务应用,如果应用很多且集群,我们还是一个一个地以手工构建,运行方式管理Docker就显得力不从心了。所以后面笔者会介绍Docker另一个多容器管理的利器Docker Compose,到时我们再把myredis部署上去,敬请期待。