MikeTech Docker 容器化实战

2021/01/05

MikeTech 由两个组件构成,Java 应用和 MySQL 数据库,在传统情况下,新部署服务的时候服务器必须去手动安装Java环境和MySQL环境,并且每次更新代码部署的时候也很不方便,我想通过 Docker 来简化部署与持续集成流程。

Docker 介绍

Docker 已经不是什么新潮的技术了,在 2016 年我开发天气 App Flat Weather 的时候,就是使用 Docker 来封装部署我的后端服务。通俗的说,Docker 可以把一个 Web应用或者容器与他的运行环境封装成一个镜像,部署在一个Docker容器中运行,容器内包含一个简化的 Linux 系统,运行环境,和你的应用。这样每次部署的时候只要把镜像推倒服务器上就行了,不用担心运行环境的问题。

Docker 镜像生成

想要部署容器,首先需要镜像,想要生成镜像,就得先编写构建镜像的 Dockerfile 文件。

Dockerfile 编写

单个应用的镜像生成非常简单,只要学会编写 Dockerfile 就行了,Dockerfile 是一个用于构建镜像的文本。Dockerfile 正常会保存在项目的根目录下,下面的代码展示了一个 Java Server 的镜像生成过程。这个Dockerfile的目的是使用 Maven 编译项目为 jar 并且运行。

第一行的 FROM 规定了这个镜像的基础环境, maven 环境会同时包含java和maven的相关环境,刚好是我们需要的,之后的代码比较直观了,无非是创立临时文件夹用来编译 jar然后把编译好的app.jar拷贝到镜像的根目录,之后的 EXPOSE 规定了容器运行时候暴露的端口,因为 MikeTech 是一个 Web 应用,所以暴露了http和https协议的80和443端口,最后一行的 ENTRYPOINT规定了镜像中的应用是如何运行的,可以看出来使用java -jar 指令运行了之前编译好的 app.jar 文件。

FROM maven:3.3.3
ADD pom.xml /tmp/build/
RUN cd /tmp/build && mvn -q dependency:resolve
ADD src /tmp/build/src
# 构建应用
RUN cd /tmp/build && mvn -q -DskipTests=true package \
# 拷贝编译结果到指定目录
		&& mv target/*.jar /app.jar \
# 清理编译痕迹
		&& cd / && rm -rf /tmp/build
EXPOSE 80
EXPOSE 443
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

之后想要生成镜像的话直接运行下面的指令就好,下面的指令会根据运行目录的 Dockerfile 文件生成一个叫做miketech-web的镜像,版本为0.1。如果调试的时候使用的是 Intelij 的话直接右键 Dockerfile 也可以直接运行,生成镜像与部署镜像的相关指令与教程网上已经够多了,这里不再过多赘述。

docker build -f Dockerfile -t miketech-web:0.1 .

当然上面的 Dockerfile 也有另外一种写法,如果不想在容器运行的时候使用 maven 编译jar的话,可以手动把jar编译出来,然后编写以下的 Dockerfile 即可,下面的Dockerfile非常简单易懂,之间只是把项目的jar文件改名为app.jar拷贝到容器的根目录下然后运行。

FROM maven:3.3.3
COPY ./target/miketech_web-0.0.1-SNAPSHOT.jar /app.jar
RUN sh -c 'touch /app.jar'
EXPOSE 80
EXPOSE 443
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

现有镜像部署

如果想要部署一个mysql容器的话甚至不需要自己去编写dockerfile,使用docker镜像商店提供的mysql镜像就好了,下面的命令启动了一个基于mysql的容器,暴露3306端口,声明了两个环境变量MYSQL_ROOT_PASSWORD 和 MYSQL_DATABASE,分别表示了镜像中mysql的root密码和创建时建立的默认数据库名称。

docker run -itd --name mysql-iso-miketech -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=miketech mysql

通过上所写的,我们已经可以建立miketech和mysql的镜像并且运行在容器中,如果你真的这么做的话,miketech的容器会无法运行,原因是建立与数据库的连接失败,这是因为Docker容器的网络是相互隔离的,也就是说即使在宿主机上运行了这两个容器,这两个容器也是相互隔离的,无法知道对方的存在,miketech容器无法通过3306接口连接到mysql容器的数据库里。

想要解决上面的问题,其中一个方案是更改docker容器的网络配置,在docker run执行的时候可以通过设置容器的network为host来让容器与宿主机共享网络配置,从而不使用独立的网络配置。或者还有更好的解决方案,往下看吧。

Docker Compose 服务编排

Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。
关于YML的编写入门教程这边也不再赘述了,可以去别的网站上学习 https://www.runoob.com/docker/docker-compose.html

下面的代码是MikeTech的编排文件,其中声明了两个服务 miketech-mysql 和 miketech-web。

miketech-mysql 为数据库容器,基于官方mysql镜像,暴露3306接口,其中声明了一个volume叫做db_data,目的是为了让容器重启后还能保留数据库中的内容,如果不规定volumes的话,即使数据库存在,容器重启后也会被清空。关于Docker Volumes 的更多内容可以查看文档 Use volumes | Docker Documentation

miketech-web 是我的Java服务,其中声明了一个depends_on属性,告诉docker miketech-web 服务需要基于上面的mysql 服务。还规定了一个 SPRING_DATASOURCE_URL 环境变量用于提供mysql的地址,直接把 miketech-sql 的服务名填进去就好了,不需要使用 ip。

version: '2'
services:
  miketech-mysql:
    container_name: miketech-mysql
    image: mysql
    volumes:
      - db_data:/var/lib/mysql
    environment:
      MYSQL_USER: root
      MYSQL_ROOT_PASSWORD: PASSOWRD
      MYSQL_DATABASE: miketech
      MYSQL_ROOT_HOST: '%'
    ports:
      - "3306:3306"
    expose:
      - 3306
    restart: always
  miketech-web:
    container_name: miketech-web
    depends_on:
      - miketech-mysql
    build: .
    working_dir: /
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://miketech-mysql:3306/miketech?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: PASSOWRD
    ports:
      - "80:80"
      - "443:443"
volumes:
  db_data: {}

YML编写完成后可以使用 docker-compose up指令启动服务即可,docker会处理好剩下所有工作的。

数据库导入

之前数据库中的文件也可以方便的导入到容器里面,使用 docker exec 指令调用容器内的mysql 指令即可。

docker ps
docker exec -i e0f4be297fb7 mysql -uroot -p19960622 miketech < miketech20201226.sql

镜像管理与持续集成

Portioner

https://github.com/portainer/portainer/

如果觉得使用命令管理镜像或者部署镜像过于麻烦,可以使用 Portainer 来管理服务器上面的Docker服务,Portrainer 提供了 Web图形化界面用于管理。在服务器上执行下面的指令即可部署,

docker volume create portainer_data
docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce

Portainer 可以部署到你的服务器上直接管理本地docker,当然如果服务器没有足够的资源来运行Portainer,也可以把Portainer部署在自己的电脑上,Portainer 可以接入远程服务器上的 Docker 服务,前提是远程服务器上的 Docker 开启了外发能力。
在 /lib/systemd/system/docker.service文件中,更改ExecStart参数,保存后重启Docker服务即可开启外发能力。

vim /lib/systemd/system/docker.service
# ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
systemctl daemon-reload
service docker restart

DaoCloud

持续集成一般来说使用 Jenkins 就可以,当然如果服务器也没有足够资源运行 Jenkins 的时候,还是有第三方服务可以帮你的,比如 DaoCloud。
DaoCloud 是 2014 年成立的一个初创公司的业务,也是我的老朋友了,2016年第一次开发后端服务时我就接触了 Docker,使用他们的服务进行持续集成。 DaoCloud 可以在项目Git 提交后自动触发测试,Docker镜像生成,发布,集成与部署。
不过这么多年过去了,DaoCloud 对于免费用户的服务质量似乎没有以前那么高了,触发git拉取的时候都需要好久,如果你想长期使用的话,需要多考虑一下。

Copyright © 2015-2021 MikeTech.it. All rights reserved.

Developed By Yigang Zhou