前面我们一直在使用别人准备好的镜像,那如果我要部署一个Java项目,把它打包为一个镜像该怎么做呢?
要想自己构建镜像,必须先了解镜像的结构。
之前我们说过,镜像之所以能让我们快速跨操作系统部署应用而忽略其运行环境、配置,就是因为镜像中包含了程序运行需要的系统函数库、环境、配置、依赖。
因此,自定义镜像本质就是依次准备好程序运行的基础环境、依赖、应用本身、运行配置等文件,并且打包而成。
举个例子,我们要从0部署一个Java应用,大概流程是这样:
那因此,我们打包镜像也是分成这么几步:
上述步骤中的每一次操作其实都是在生产一些文件(系统运行环境、函数库、配置最终都是磁盘文件),所以镜像就是一堆文件的集合。
但需要注意的是,镜像文件不是随意堆放的,而是按照操作的步骤分层叠加而成,每一层形成的文件都会单独打包并标记一个唯一id,称为Layer(层)。这样,如果我们构建时用到的某些层其他人已经制作过,就可以直接拷贝使用这些层,而不用重复制作。
例如,第一步中需要的Linux运行环境,通用性就很强,所以Docker官方就制作了这样的只包含Linux运行环境的镜像。我们在制作java镜像时,就无需重复制作,直接使用Docker官方提供的CentOS或Ubuntu镜像作为基础镜像。然后再搭建其它层即可,这样逐层搭建,最终整个Java项目的镜像结构如图所示:
由于制作镜像的过程中,需要逐层处理和打包,比较复杂,所以Docker就提供了自动打包镜像的功能。我们只需要将打包的过程,每一层要做的事情用固定的语法写下来,交给Docker去执行即可。
而这种记录镜像结构的文件就称为Dockerfile,其对应的语法可以参考官方文档:
其中的语法比较多,比较常用的有:
指令 | 说明 | 示例 |
---|---|---|
FROM | 指定基础镜像 | FROM centos:6 |
ENV | 设置环境变量,可在后面指令使用 | ENV key value |
COPY | 拷贝本地文件到镜像的指定目录 | COPY ./xx.jar /tmp/app.jar |
RUN | 执行Linux的shell命令,一般是安装过程的命令 | RUN yum install gcc |
EXPOSE | 指定容器运行时监听的端口,是给镜像使用者看的 | EXPOSE 8080 |
ENTRYPOINT | 镜像中应用的启动命令,容器运行时调用 | ENTRYPOINT java -jar xx.jar |
例如,要基于Ubuntu镜像来构建一个Java应用,其Dockerfile内容如下:
# 指定基础镜像
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录、容器内时区
ENV JAVA_DIR=/usr/local
ENV TZ=Asia/Shanghai
# 拷贝jdk和java项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar
# 设定时区
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 安装JDK
RUN cd $JAVA_DIR \
&& tar -xf ./jdk8.tar.gz \
&& mv ./jdk1.8.0_144 ./java8
# 配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin
# 指定项目监听的端口
EXPOSE 8080
# 入口,java项目的启动命令
ENTRYPOINT ["java", "-jar", "/app.jar"]
思考一下:以后我们会有很多很多java项目需要打包为镜像,他们都需要Linux系统环境、JDK环境这两层,只有上面的3层不同(因为jar包不同)。如果每次制作java镜像都重复制作前两层镜像,是不是很麻烦。
所以,就有人提供了基础的系统加JDK环境,我们在此基础上制作java镜像,就可以省去JDK的配置了:
# 基础镜像
FROM openjdk:11.0-jre-buster
# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 拷贝jar包
COPY docker-demo.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]
是不是简单多了。
当Dockerfile文件写好以后,就可以利用命令来构建镜像了。
在课前资料中,我们准备好了一个demo项目及对应的Dockerfile:
1.首先,我们将课前资料提供的docker-demo.jar包以及Dockerfile拷贝到虚拟机的/root/demo目录:
2.然后,执行命令,构建镜像:
# 进入镜像目录
cd /root/demo
# 开始构建
docker build -t docker-demo:1.0 .
命令说明:
. :
最后的点是指构建时Dockerfile所在路径,由于我们进入了demo目录,所以指定的是.代表当前目录,也可以直接指定Dockerfile目录:# 直接指定Dockerfile目录
docker build -t docker-demo:1.0 /root/demo
结果:
1.查看镜像列表:
# 查看镜像列表:
docker images
1.运行该镜像:
# 1.创建并运行容器
docker run -d --name dd -p 8080:8080 docker-demo:1.0
# 2.查看容器
docker ps
2.访问
# 3.访问
curl localhost:8080/hello/count
3.查看日志
docker logs -f dd
上面我们创建了一个Java项目的容器,而Java项目往往需要访问其它各种中间件,例如MySQL、Redis等。现在,我们的容器之间能否互相访问呢?我们来测试一下
首先,我们查看下MySQL容器的详细信息,重点关注其中的网络IP地址:
1.用基本命令,寻找Networks.bridge.IPAddress属性
# 1.用基本命令,寻找Networks.bridge.IPAddress属性
docker inspect mysql
# 也可以使用format过滤结果
docker inspect --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' mysql
# 得到IP地址如下:
172.17.0.2
2.进入dd容器,在容器内,通过ping命令测试网络
# 2.然后通过命令进入dd容器
docker exec -it dd bash
# 3.在容器内,通过ping命令测试网络
ping 172.17.0.2
发现可以互联,没有问题。
但是,容器的网络IP其实是一个虚拟的IP,其值并不固定与某一个容器绑定,如果我们在开发时写死某个IP,而在部署时很可能MySQL容器的IP会发生变化,连接会失败。
我们必须借助于docker的网络功能来解决这个问题
官方文档:https://docs.docker.com/engine/reference/commandline/network/
常见命令有:
命令 | 说明 | 文档地址 |
---|---|---|
docker network create | 创建一个网络 | docker network create |
docker network ls | 查看所有网络 | docs.docker.com |
docker network rm | 删除指定网络 | docs.docker.com |
docker network prune | 清除未使用的网络 | docs.docker.com |
docker network connect | 使指定容器连接加入某网络 | docs.docker.com |
docker network disconnect | 使指定容器连接离开某网络 | docker network disconnect |
docker network inspect | 查看网络详细信息 | docker network inspect |
1.首先通过命令创建一个网络
# 1.首先通过命令创建一个网络
docker network create coke
2…然后查看网络
# 2.然后查看网络 ( 其中,除了coke以外,其它都是默认的网络)
docker network ls
3.让dd和mysql都加入该网络,注意,在加入网络时可以通过–alias给容器起别名
# 3.让dd和mysql都加入该网络,注意,在加入网络时可以通过--alias给容器起别名
# 这样该网络内的其它容器可以用别名互相访问!
# 3.1.mysql容器,指定别名为db,另外每一个容器都有一个别名是容器名
docker network connect coke mysql --alias db
# 3.2.db容器,也就是我们的java项目
docker network connect coke dd
4.进入dd容器,尝试利用别名访问db
# 4.进入dd容器,尝试利用别名访问db
# 4.1.进入容器
docker exec -it dd bash
# 4.2.用db别名访问
ping db
5.进入dd容器,用容器名访问
# 4.3.用容器名访问
ping mysql
OK,现在无需记住IP地址也可以实现容器互联了。
总结:
好了,我们已经熟悉了Docker的基本用法,接下来可以尝试部署项目了。
项目说明:
部署的容器及端口说明:
项目 | 容器名 | 端口 | 备注 |
---|---|---|---|
invoice | invoice | 19009 | 发票系统管理的后端API入口 |
invoice-web | nginx | 8899 | 发票系统管理的前端入口 |
mysql | mysql | 3306 | 发票系统管理的前端入口 |
DockerFile
# 基础镜像
FROM openjdk:8-jre-buster
# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 拷贝jar包
COPY invoice-0.0.1-SNAPSHOT.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]
因为项目中分别用到了MySQL、nginx和redis,所以提前准备好这些容器(并准备好数据库中的数据)
MySQL、nginx、redis容器 将容器放到同一个网络中,之后直接使用容器名字访问即可
# 创建网络(名为invoice)
docker network create invoice
# 查看所有网络
docker network ls
1.创建容器
创建nginx容器笔记:https://blog.csdn.net/cygqtt/article/details/135665012
2.nginx容器创建之后我们需要将
nginx容器
加入到invoice
网络中
# 查看所有网络
docker network ls
# 将容器加入到网络中
docker network connect invoice nginx
3.查看网络详细信息
docker network inspect invoice
1.创建容器
创建redis容器笔记:https://blog.csdn.net/cygqtt/article/details/135665012
2.redis容器创建之后我们需要将
redis容器
加入到invoice
网络中
# 查看所有网络
docker network ls
# 将容器加入到网络中
docker network connect invoice redis
3.查看网络详细信息
docker network inspect invoice
1.创建容器
创建MySQL容器笔记:https://blog.csdn.net/cygqtt/article/details/135665012
2.MySQL容器创建之后我们需要将
MySQL容器
加入到invoice
网络中
# 查看所有网络
docker network ls
# 将容器加入到网络中
docker network connect invoice mysql
3.查看网络详细信息
docker network inspect invoice
1.运行sql文件
1.可以使用以下两种方式来命名 Dockerfile 文件:
2.Dokerfile内容
# 基础镜像
FROM openjdk:8-jre-buster
# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 拷贝jar包
COPY invoice-0.1.2-SNAPSHOT.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]
1.将Dockerfile和jar包一起上传到虚拟机
2.构建镜像
# 1.构建项目镜像,不指定tag,则默认为latest
docker build -t invoice .
2,查看镜像
# 2.查看镜像
docker images
3.创建并运行容器
# 创建并运行一个tomcat容器
docker run -d --name tomcat --network invoice -p 8080:8080 tomcat
# 创建并运行容器,并通过--network将其加入hmall网络,这样才能通过容器名访问mysql
docker run -d --name invoice --network invoice -p 19009:19009 invoice
4.查看启动日志
docker logs invoice
1.创建目录用于存放前端代码
mkdir -p /usr/local/nginx/html/invoice_web
2.将打包好的前端代表拷贝到目录
/usr/local/nginx/html/invoice_web
下
3.配置nginx
vim /usr/local/nginx/nginx/nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/json;
sendfile on;
keepalive_timeout 65;
client_max_body_size 1000M; #(设置客户端请求体最大值)
client_body_buffer_size 1000M; #(配置请求体缓存区大小)
fastcgi_intercept_errors on;
server {
listen 8899;
server_name _;
location /api/ {
# 这里配置代理到后端服务的地址
proxy_pass http://invoice:19009/;
}
location / {
# 这里配置前端资源的路径(容器内部路径)
root /usr/share/nginx/html/invoice-web;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}
}
4.重启nginx容器
# 重启nginx容器(使配置文件生效)
docker restart nginx