jaybril

jaybril 查看完整档案

广州编辑山东大学  |  计算机 编辑编程无界  |  技术写手 编辑 juejin.im/user/5b64835b518825615d2fcfea 编辑
编辑

对安静情有独钟。。
体育界忠实粉丝。。
自命不凡幻想家。。
关注公众号【编程无界】有惊喜

个人动态

jaybril 回答了问题 · 4月20日

列表分页,数据重复,如何解决

1)如果数据量不大,返回全部,前端分页
2)如果数据量大且ID自增,每次请求传递最大ID,查询范围为小于等于当前最大ID的数据
3)如果数据量大且ID不自增,请求传递当前时间
4)如果查询条件多且数据经常更新,每次请求传递ids,后端过滤重复的ids后返回

关注 5 回答 5

jaybril 回答了问题 · 3月13日

解决服务器(centos)配置启动nginx,浏览器无法访问?

你这是配负载均衡啊,8001这个服务的nginx配置呢??

关注 3 回答 2

jaybril 评论了文章 · 2019-05-13

亲历,创业公司如何死掉的

亲历,创业公司如何死掉的

虽然目前尚未离职,但期限迫在眉睫。失望过后还得整顿自己再出发,新开始之前老感觉缺点东西,还是回顾一下东家是如何在这条创业路上死掉的,顺便记录下,已警示后来的自己。

1. 目标宏达,出发点错误

大Boss目标太过宏大,短时间内无法开发,项目周期太长, 项目落地时出发点偏离,导致刚开始就造重复的轮子。

2. 技术团队人员分布不合理

领导在寻求技术团队领导太过感性(后来才知道谈了一个就确定了),技术领导招人的方向出现偏差(以前端全栈为标准),导致出现5个前端没有后端,迫不得已才招1个后端,技术选型也很不友好(用到的技术实践的公司很少)。

3. 缺少专业产品经理和懂市场人员

后来才知道产品经理是UI零时转型,产品开发没有切合市场需求, 想当然。

4. 领导不懂技术,缺少团队赋能

大boss传统企业出身,不懂互联网技术,作为创业公司很少和团队见面,也不去主导产品的方向。

5. 工资迟发,影响氛围

工资长期迟发, 导致员工情绪很大程度受到了影响,人员变动也频繁。

...

我得到了什么

  1. 学习领导的的一些理念。
  2. 看清楚创业公司不应犯的错误。
  3. 主导从0到1的项目过程,尝试做了小团队的管理者。
  4. 发现自己对产品经理非常感兴趣,除了coding,产品将是我的第二职业,非要排出的第三的话,不想做厨师的coder不是好PM
  5. 对于如何生活,有了新的认识,当然也是受领导的感染。
  6. 创业不是有钱就能干的,很大可能是一帮志同道合的人没钱给干成了, 所以初期的创业公司招人这个路数风险很大, 找人才是上策。
  7. 见识了当下融资的困境和面临的问题, 必须要有的硬核担当,总的来说,当下的经济形势,资本已将对待A轮公司的标准移到了种子轮或者天使轮, 所以创业更需要硬核产品,砸地必须有坑的产品,这也资本才可能青睐。
  8. 责任心和正道必须是创业者必须有的基本素质。
  9. 选择前要慎重, 选择后要要极力证明自己选择的正确性,而不是盲目草率的重新选择。
  10. 融资,不光融的是钱, 更重要的是资源, 怎么把盘子做大, 所以好多公司不是因为钱不够,是想做大盘子而已。

后续
非常善良的人其实最是合创业, 但是绝对不能单独带领团队,很多情况判断会出现错误, 所以一定要有个合伙人唱白脸,在关键时刻来制止错误的决定, 我司可能就缺这么一个人。
领导看过后觉得写得很好, 欣慰之至,还点赞了, 期望新的开始能让自己变得更好更强大,也希望能给新东家贡献自己的力量。
查看原文

jaybril 关注了问题 · 2019-03-03

API网关(Zuul)如何权限控制各个微服务的接口

前景:当前项目使用Spring CLoud全家桶,用户鉴权逻辑统一写在Zuul的filter中,到达Zuul的请求中携带的token不正确,则返回鉴权失败,否则路由到指定微服务,filter中人为跳过登录接口(/login)校验
问题:系统运行业务后发现很多场景下不需要用户鉴权,例如网站首页的文章查看等,这时候如果还是在filter中一个个加上要跳过鉴权的接口uri,我感觉有点傻。请问有比较优雅的方式,可以实现控制接口是否需要被Zuul鉴权。

关注 5 回答 4

jaybril 收藏了文章 · 2019-02-19

如何用Dockerfile构建镜像

本文旨在用通俗的语言讲述枯燥的知识

前面讲到镜像的构建时,讲述的是用commit的方式构建镜像,而Dockerfile是另一种构建镜像的方式。

Dockerfile构建镜像是以基础镜像为基础的,Dockerfile是一个文本文件,内容是用户编写的一些docker指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

Dockerfile的基本指令有十三个,分别是:FROM、MAINTAINER、RUN、CMD、EXPOSE、ENV、ADD、COPY、ENTRYPOINT、VOLUME、USER、WORKDIR、ONBUILD

从前面的内容可以看出,要构建一个容器,需要做很多的工作,设置很多的配置,如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。
这个脚本就是 Dockerfile。

因此学会用Dockerfile来构建镜像,是非常有必要的。学习Dockerfile之前,我们先来学习一些Dockerfile常用的指令。

文章提纲:

  1. Dockerfile常用指令
  2. Dockerfile的编写
  3. 用Dockerfile构建镜像
  4. 彩蛋

1 Dockerfile常用指令

类型命令
基础镜像信息FROM
维护者信息MAINTAINER
镜像操作指令RUN、COPY、ADD、EXPOSE、WORKDIR、ONBUILD、USER、VOLUME等
容器启动时执行指令CMD、ENTRYPOINT
1.1、FROM :指定基础镜像

所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个nginx镜像的容器,再进行修改一样,基础镜像是必须指定的。而FROM就是指定基础镜 像,因此一个Dockerfile中FROM是必备的指令,并且必须是第一条指令。
如:指定ubuntu的14版本作为基础镜像

FROM ubuntu:14
1.2、 RUN:执行命令

RUN指令在新镜像内部执行的命令,如:执行某些动作、安装系统软件、配置系统信息之类,

格式如下两种:

  • 1)shell格式:RUN< command > ,就像直接在命令行中输入的命令一样。

如在nginx里的默认主页中写”hello“:

RUN echo 'hello ' >/etc/nginx/html/index.html
  • 2)exec格式:RUN ["可执行文件", "参数1", "参数2"]

如在新镜像中用yum方式安装nginx:

RUN ["yum","install","nginx"]

注:多行命令不要写多个RUN,原因是Dockerfile中每一个指令都会建立一层.多少个RUN就构建了多少层镜像,会造成镜像的臃肿、多层,不仅仅增加了构件部署的时间,还容易出错,RUN书写时的换行符是\

1.3、COPY:复制文件

COPY命令用于将宿主机器上的的文件复制到镜像内,如果目的位置不存在,Docker会自动创建。但宿主机器用要复制的目录必须是和Dockerfile文件统计目录下。

格式:

COPY [--chown=<user>:<group>] <源路径>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]

如把宿主机中的package.json文件复制到容器中/usr/src/app/目录下:

COPY package.json /usr/src/app/
1.4、CMD:容器启动命令

CMD命令用于容器启动时需要执行的命令,CMD在Dockerfile中只能出现一次,如果出现多个,那么只有最后一个会有效。
其作用是在启动容器的时候提供一个默认的命令项。如果用户执行docker run的时候提供了命令项,就会覆盖掉这个命令,没提供就会使用构建时的命令。

格式:

shell 格式:CMD <命令>
exec 格式:CMD ["可执行文件", "参数1", "参数2"...]

如容器启动时进入bash:

CMD /bin/bash

也可以用exec写法:

CMD ["/bin/bash"]
1.5 MAINTAINER:指定作者

用来指定dockerfile的作者名称和邮箱,主要作用是为了标识软件的所有者是谁。
语法:

MAINTAINER <name> <email>

如:

MAINTAINER autor_jiabuli 6766633@qq.com
1.6、EXPOSE:暴露端口

EXPOSE命名适用于设置容器对外映射的容器端口号,如tomcat容器内使用的端口8081,则用EXPOSE命令可以告诉外界该容器的8081端口对外,在构建镜像时
用docker run -p可以设置暴露的端口对宿主机器端口的映射。

语法:

EXPOSE <端口1> [<端口2>...]

如:

EXPOSE 8081

EXPOSE 8081 其实等价于 docker run -p 8081 当需要把8081端口映射到宿主机中的某个端口(如8888)以便外界访问时,则可以用docker run -p 8888:8081

1.7、WORKDIR:配置工作目录

WORKDIR命令是为RUN、CMD、ENTRYPOINT指令配置工作目录。其效果类似于Linux命名中的cd命令,用于目录的切换,但是和cd不一样的是:如果切换到的目录不存在,WORKDIR会为此创建目录。

语法:

WORKDIR path

如需要在nginx目录下创建一个hello.txt的文件:

##进入/usr/local/nginx目录下
WORKDIR /usr/local/nginx

##进入/usr/local/nginx中的html目录下
WORKDIR html

## 在html目录下创建了一个hello.txt文件
RUN echo 'hello' > hello.txt
1.8、ENTRYPOINT:容器启动执行命名

ENTRYPOINT的作用和用法和CMD一模一样,但是ENTRYPOINT有和CMD有2处不一样:

  1. CMD的命令会被docker run的命令覆盖而ENTRYPOINT不会
  2. CMD和ENTRYPOINT都存在时,CMD的指令变成了ENTRYPOINT的参数,并且此CMD提供的参数会被 docker run 后面的命令覆盖
1.9、VOLUME

VOLUME用来创建一个可以从本地主机或其他容器挂载的挂载点。例如我们知道tomcat的webapps目录是放web应用程序代码的地方,此时我们要把webapps目录挂载为匿名卷,这样任何写入webapps中的心都不会被记录到容器的存储层,让容器存储层无状态化。

格式:

VOLUME ["path"]

如创建tomcat的webapps目录的一个挂载点

VOLUME /usr/local/tomcat/webapps

这样,在运行容器时,也可以用过docker run -v来把匿名挂载点挂载都宿主机器上的某个目录,如

docker run -d -v /home/tomcat_webapps:/usr/local/tomcat/webapps
1.10、 USER
USER命令用于指定当前望下执行的用户,需要注意的是这个用户必须是已经存在,否则无法指定。它的用法和WORKDIR有点像,切换用户。

格式:

USER daemon
1.11、ADD

作用和使用方法和COPY一模一样,在此不重复讲述。

1.12、ONBUILD

ONBUILD用于配置当前所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。
意思就是:这个镜像创建后,如果其它镜像以这个镜像为基础,会先执行这个镜像的ONBUILD命令
格式:

ONBUILD [INSTRUCTION]
1.13、ENV:设置环境变量

ENV命名用于设置容器的环境变量,这些变量以”key=value”的形式存在,在容器内被脚本或者程序调用,容器运行的时候这个变量也会保留。

格式:

  • 1) 设置一个: ENV <key> <value>
  • 2) 设置多个:ENV <key1>=<value1> <key2>=<value2>...

如设置一个环境变量JAVA_HOME,接下来的命名就可以使用这个变量:

ENV JAVA_HOME /opt/jdk
ENV PATH $PATH:$JAVA_HOME/bin

在使用ENV设置环境变量时,有几点需要注意:

  • 1)具有传递性,也就是当前镜像被用作其它镜像的基础镜像时,新镜像会拥有当前这个基础镜像所有的环境变量
  • 2)ENV定义的环境变量,可以在dockerfile被后面的所有指令(CMD除外)中使用,但不能被docker run 的命令参数引用

如:

ENV tomcat_home_name tomcat_7
RUN mkdir $tomcat_home_name
  • 3)除了ENV之外,docker run -e 也可以设置环境变量传入容器内。

如:

docker run -d tomcat -e "tomcat_home_name=tomcat_7"

这样我们进入容器内部用ENV可以看到tomcat_home_name这个环境变量。

2 Dockerfile的编写

我们先看一个例子

#在centos上安装nginx
FROM centos
#标明著作人的名称和邮箱
MAINTAINER jiabuli 649917837@qq.com
#测试一下网络环境
RUN ping -c 1 www.baidu.com
#安装nginx必要的一些软件
RUN yum -y install gcc make pcre-devel zlib-devel tar zlib
#把nginx安装包复制到/usr/src/目录下
ADD nginx-1.15.8.tar.gz /usr/src/
#切换到/usr/src/nginx-1.15.8编译并且安装nginx
RUN cd /usr/src/nginx-1.15.8 \
    && mkdir /usr/local/nginx \
    && ./configure --prefix=/usr/local/nginx && make && make install \
    && ln -s /usr/local/nginx/sbin/nginx /usr/local/sbin/ \
    && nginx
#删除安装nginx安装目录
RUN rm -rf /usr/src/nginx-nginx-1.15.8
#对外暴露80端口
EXPOSE 80
#启动nginx
CMD ["nginx", "-g", "daemon off;"]

上面的注释已经讲的非常清楚,其实不难发现,上面的例子就是类似于在centos系统上安装一个nginx的的一个过程,因此编写Dockerfile构建镜像就和在Linux上安装软件的流程几乎是一模一样的。所以我们在编写Dockerfile来构建镜像时,可以先思考在Linux上安装该软件的流程,再用Dockerfile提供的指令转化到Dockerfile中即可。

3.用Dockerfile构建镜像

用Dockerfile的核心在于编写Dockerfile,但是编写完之后我们需要知道怎么使用Dockerfile来构建镜像,下面以构建nginx镜像为例来简要说明构建流程

3.1 上传安装包

首先我们需要把要构建的软件安装包上传到服务器中,我们可以在服务器目录上创建一个专门的文件夹,如:/var/nginx_build,然后把从nginx官网下载的nginx-1.15.8.tar.gz安装包上传到这个目录里。

3.2 编写Dockerfile

如何编写nginx的Dockerfile上面已经详细介绍,现在我们只需把编写好的Dockerfile上传到/var/nginx_build目录下,当然你也可以在服务器上直接编写Dockerfile,但是要记得一定保证Dockerfile文件和安装包在一个目录下。

3.3 运行构建命令构建

docker build 命令用于使用 Dockerfile 创建镜像。
格式:

  docker build [OPTIONS] PATH | URL | -

OPTIONS有很多指令,下面列举几个常用的:

  • --build-arg=[] :设置镜像创建时的变量;
  • -f :指定要使用的Dockerfile路径;
  • --force-rm :设置镜像过程中删除中间容器;
  • --rm :设置镜像成功后删除中间容器;
  • --tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;

因此我们构建nginx可以用以下命令:

docker build -t nginx:v1.0 .

当Dockerfile和当前执行命令的目录不在同一个时,我们也可以指定Dockerfile,如

docker build -f /var/nginx_build/Dockerfile .

执行命名之后,会看到控制台逐层输出构建内容,直到输出两个Successfully即为构建成功。

4. 彩蛋

写了很多篇docker的文章,为了方便开发者们学习和查阅docker的基础知识,我做了一份完整的docker基础整理,发布在gitchat上面,有兴趣的读者可以加入一起学习,我这里还有6个免费名额,如有需要,可以加微信:sisi-ceo 索要。


觉得本文对你有帮助?请分享给更多人
关注「编程无界」,提升装逼技能

查看原文

jaybril 发布了文章 · 2019-02-19

如何用Dockerfile构建镜像

本文旨在用通俗的语言讲述枯燥的知识

前面讲到镜像的构建时,讲述的是用commit的方式构建镜像,而Dockerfile是另一种构建镜像的方式。

Dockerfile构建镜像是以基础镜像为基础的,Dockerfile是一个文本文件,内容是用户编写的一些docker指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

Dockerfile的基本指令有十三个,分别是:FROM、MAINTAINER、RUN、CMD、EXPOSE、ENV、ADD、COPY、ENTRYPOINT、VOLUME、USER、WORKDIR、ONBUILD

从前面的内容可以看出,要构建一个容器,需要做很多的工作,设置很多的配置,如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。
这个脚本就是 Dockerfile。

因此学会用Dockerfile来构建镜像,是非常有必要的。学习Dockerfile之前,我们先来学习一些Dockerfile常用的指令。

文章提纲:

  1. Dockerfile常用指令
  2. Dockerfile的编写
  3. 用Dockerfile构建镜像
  4. 彩蛋

1 Dockerfile常用指令

类型命令
基础镜像信息FROM
维护者信息MAINTAINER
镜像操作指令RUN、COPY、ADD、EXPOSE、WORKDIR、ONBUILD、USER、VOLUME等
容器启动时执行指令CMD、ENTRYPOINT
1.1、FROM :指定基础镜像

所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个nginx镜像的容器,再进行修改一样,基础镜像是必须指定的。而FROM就是指定基础镜 像,因此一个Dockerfile中FROM是必备的指令,并且必须是第一条指令。
如:指定ubuntu的14版本作为基础镜像

FROM ubuntu:14
1.2、 RUN:执行命令

RUN指令在新镜像内部执行的命令,如:执行某些动作、安装系统软件、配置系统信息之类,

格式如下两种:

  • 1)shell格式:RUN< command > ,就像直接在命令行中输入的命令一样。

如在nginx里的默认主页中写”hello“:

RUN echo 'hello ' >/etc/nginx/html/index.html
  • 2)exec格式:RUN ["可执行文件", "参数1", "参数2"]

如在新镜像中用yum方式安装nginx:

RUN ["yum","install","nginx"]

注:多行命令不要写多个RUN,原因是Dockerfile中每一个指令都会建立一层.多少个RUN就构建了多少层镜像,会造成镜像的臃肿、多层,不仅仅增加了构件部署的时间,还容易出错,RUN书写时的换行符是\

1.3、COPY:复制文件

COPY命令用于将宿主机器上的的文件复制到镜像内,如果目的位置不存在,Docker会自动创建。但宿主机器用要复制的目录必须是和Dockerfile文件统计目录下。

格式:

COPY [--chown=<user>:<group>] <源路径>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]

如把宿主机中的package.json文件复制到容器中/usr/src/app/目录下:

COPY package.json /usr/src/app/
1.4、CMD:容器启动命令

CMD命令用于容器启动时需要执行的命令,CMD在Dockerfile中只能出现一次,如果出现多个,那么只有最后一个会有效。
其作用是在启动容器的时候提供一个默认的命令项。如果用户执行docker run的时候提供了命令项,就会覆盖掉这个命令,没提供就会使用构建时的命令。

格式:

shell 格式:CMD <命令>
exec 格式:CMD ["可执行文件", "参数1", "参数2"...]

如容器启动时进入bash:

CMD /bin/bash

也可以用exec写法:

CMD ["/bin/bash"]
1.5 MAINTAINER:指定作者

用来指定dockerfile的作者名称和邮箱,主要作用是为了标识软件的所有者是谁。
语法:

MAINTAINER <name> <email>

如:

MAINTAINER autor_jiabuli 6766633@qq.com
1.6、EXPOSE:暴露端口

EXPOSE命名适用于设置容器对外映射的容器端口号,如tomcat容器内使用的端口8081,则用EXPOSE命令可以告诉外界该容器的8081端口对外,在构建镜像时
用docker run -p可以设置暴露的端口对宿主机器端口的映射。

语法:

EXPOSE <端口1> [<端口2>...]

如:

EXPOSE 8081

EXPOSE 8081 其实等价于 docker run -p 8081 当需要把8081端口映射到宿主机中的某个端口(如8888)以便外界访问时,则可以用docker run -p 8888:8081

1.7、WORKDIR:配置工作目录

WORKDIR命令是为RUN、CMD、ENTRYPOINT指令配置工作目录。其效果类似于Linux命名中的cd命令,用于目录的切换,但是和cd不一样的是:如果切换到的目录不存在,WORKDIR会为此创建目录。

语法:

WORKDIR path

如需要在nginx目录下创建一个hello.txt的文件:

##进入/usr/local/nginx目录下
WORKDIR /usr/local/nginx

##进入/usr/local/nginx中的html目录下
WORKDIR html

## 在html目录下创建了一个hello.txt文件
RUN echo 'hello' > hello.txt
1.8、ENTRYPOINT:容器启动执行命名

ENTRYPOINT的作用和用法和CMD一模一样,但是ENTRYPOINT有和CMD有2处不一样:

  1. CMD的命令会被docker run的命令覆盖而ENTRYPOINT不会
  2. CMD和ENTRYPOINT都存在时,CMD的指令变成了ENTRYPOINT的参数,并且此CMD提供的参数会被 docker run 后面的命令覆盖
1.9、VOLUME

VOLUME用来创建一个可以从本地主机或其他容器挂载的挂载点。例如我们知道tomcat的webapps目录是放web应用程序代码的地方,此时我们要把webapps目录挂载为匿名卷,这样任何写入webapps中的心都不会被记录到容器的存储层,让容器存储层无状态化。

格式:

VOLUME ["path"]

如创建tomcat的webapps目录的一个挂载点

VOLUME /usr/local/tomcat/webapps

这样,在运行容器时,也可以用过docker run -v来把匿名挂载点挂载都宿主机器上的某个目录,如

docker run -d -v /home/tomcat_webapps:/usr/local/tomcat/webapps
1.10、 USER
USER命令用于指定当前望下执行的用户,需要注意的是这个用户必须是已经存在,否则无法指定。它的用法和WORKDIR有点像,切换用户。

格式:

USER daemon
1.11、ADD

作用和使用方法和COPY一模一样,在此不重复讲述。

1.12、ONBUILD

ONBUILD用于配置当前所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。
意思就是:这个镜像创建后,如果其它镜像以这个镜像为基础,会先执行这个镜像的ONBUILD命令
格式:

ONBUILD [INSTRUCTION]
1.13、ENV:设置环境变量

ENV命名用于设置容器的环境变量,这些变量以”key=value”的形式存在,在容器内被脚本或者程序调用,容器运行的时候这个变量也会保留。

格式:

  • 1) 设置一个: ENV <key> <value>
  • 2) 设置多个:ENV <key1>=<value1> <key2>=<value2>...

如设置一个环境变量JAVA_HOME,接下来的命名就可以使用这个变量:

ENV JAVA_HOME /opt/jdk
ENV PATH $PATH:$JAVA_HOME/bin

在使用ENV设置环境变量时,有几点需要注意:

  • 1)具有传递性,也就是当前镜像被用作其它镜像的基础镜像时,新镜像会拥有当前这个基础镜像所有的环境变量
  • 2)ENV定义的环境变量,可以在dockerfile被后面的所有指令(CMD除外)中使用,但不能被docker run 的命令参数引用

如:

ENV tomcat_home_name tomcat_7
RUN mkdir $tomcat_home_name
  • 3)除了ENV之外,docker run -e 也可以设置环境变量传入容器内。

如:

docker run -d tomcat -e "tomcat_home_name=tomcat_7"

这样我们进入容器内部用ENV可以看到tomcat_home_name这个环境变量。

2 Dockerfile的编写

我们先看一个例子

#在centos上安装nginx
FROM centos
#标明著作人的名称和邮箱
MAINTAINER jiabuli 649917837@qq.com
#测试一下网络环境
RUN ping -c 1 www.baidu.com
#安装nginx必要的一些软件
RUN yum -y install gcc make pcre-devel zlib-devel tar zlib
#把nginx安装包复制到/usr/src/目录下
ADD nginx-1.15.8.tar.gz /usr/src/
#切换到/usr/src/nginx-1.15.8编译并且安装nginx
RUN cd /usr/src/nginx-1.15.8 \
    && mkdir /usr/local/nginx \
    && ./configure --prefix=/usr/local/nginx && make && make install \
    && ln -s /usr/local/nginx/sbin/nginx /usr/local/sbin/ \
    && nginx
#删除安装nginx安装目录
RUN rm -rf /usr/src/nginx-nginx-1.15.8
#对外暴露80端口
EXPOSE 80
#启动nginx
CMD ["nginx", "-g", "daemon off;"]

上面的注释已经讲的非常清楚,其实不难发现,上面的例子就是类似于在centos系统上安装一个nginx的的一个过程,因此编写Dockerfile构建镜像就和在Linux上安装软件的流程几乎是一模一样的。所以我们在编写Dockerfile来构建镜像时,可以先思考在Linux上安装该软件的流程,再用Dockerfile提供的指令转化到Dockerfile中即可。

3.用Dockerfile构建镜像

用Dockerfile的核心在于编写Dockerfile,但是编写完之后我们需要知道怎么使用Dockerfile来构建镜像,下面以构建nginx镜像为例来简要说明构建流程

3.1 上传安装包

首先我们需要把要构建的软件安装包上传到服务器中,我们可以在服务器目录上创建一个专门的文件夹,如:/var/nginx_build,然后把从nginx官网下载的nginx-1.15.8.tar.gz安装包上传到这个目录里。

3.2 编写Dockerfile

如何编写nginx的Dockerfile上面已经详细介绍,现在我们只需把编写好的Dockerfile上传到/var/nginx_build目录下,当然你也可以在服务器上直接编写Dockerfile,但是要记得一定保证Dockerfile文件和安装包在一个目录下。

3.3 运行构建命令构建

docker build 命令用于使用 Dockerfile 创建镜像。
格式:

  docker build [OPTIONS] PATH | URL | -

OPTIONS有很多指令,下面列举几个常用的:

  • --build-arg=[] :设置镜像创建时的变量;
  • -f :指定要使用的Dockerfile路径;
  • --force-rm :设置镜像过程中删除中间容器;
  • --rm :设置镜像成功后删除中间容器;
  • --tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;

因此我们构建nginx可以用以下命令:

docker build -t nginx:v1.0 .

当Dockerfile和当前执行命令的目录不在同一个时,我们也可以指定Dockerfile,如

docker build -f /var/nginx_build/Dockerfile .

执行命名之后,会看到控制台逐层输出构建内容,直到输出两个Successfully即为构建成功。

4. 彩蛋

写了很多篇docker的文章,为了方便开发者们学习和查阅docker的基础知识,我做了一份完整的docker基础整理,发布在gitchat上面,有兴趣的读者可以加入一起学习,我这里还有6个免费名额,如有需要,可以加微信:sisi-ceo 索要。


觉得本文对你有帮助?请分享给更多人
关注「编程无界」,提升装逼技能

查看原文

赞 17 收藏 11 评论 0

jaybril 收藏了文章 · 2019-02-13

SpringBoot下使用定时任务的方式全揭秘

本文旨在用通俗的语言讲述枯燥的知识

定时任务作为一种系统调度工具,在一些需要有定时作业的系统中应用广泛,如每逢某个时间点统计数据、在将来某个时刻执行某些动作...定时任务在主流开发语言均提供相应的API供开发者调用,在Java中,实现定时任务有很多种方式,原生的方式实现一个完整定时任务需要由Timer、TimerTask两个类,Timer是定时器类,用来按计划开启后台线程执行指定任务,TimerTask一个抽象类,它的子类代表一个可以被Timer计划的任务。除此之外,还可以用ScheduledExecutorService类或者使用第三方jar库Quartz,其中Quartz是一个优秀的定时任务框架,发展至今已经非常成熟,以致后来其他的定时任务框架的核心思想或底层大多源于Quartz。

springboot作为Java的一种开发框架,在springboot项目中实现定时任务不仅可以使用Java提供的原生方式,还可以使用springboot提供的定时任务API,下面,小编把Java原生和springboot所有的实现定时任务的方式做一个整合。

文章提纲:
1、使用线程
2、使用Timer类
3、使用ScheduledExecutorService类
4、使用Quartz
5、使用spring的@Scheduled注解
6、cron表达式

1. 线程实现

利用线程可以设定休眠时间的方式可以实现简单的定时任务逻辑。

    public static void main(String[] args){
        //定时任务间隔时间
        int sleepTime=2*1000;
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        System.out.println("Thread方式执行一次定时任务");
                        //线程休眠规定时间
                        Thread.sleep(sleepTime);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

2. Timer类

Timer类允许调度一个TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行。

    public static void main(String[] args){
        int sleepTime=2*1000;
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("Timer方式执行一次定时任务");
            }
        };
        new Timer().schedule(timerTask,1,sleepTime);
    }

3. ScheduledExecutorService类

ScheduledExecutorService,是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。
因此,基于ScheduledExecutorService类的定时任务类,归根到底也是基于线程的调度实现的。

    public static void main(String[] args){
        int sleepTime=2*1000;
        ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
        scheduledExecutor.scheduleAtFixedRate(
                new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("ScheduledExecutorService方式执行一次定时任务");
                    }
                }
        ,1,sleepTime, TimeUnit.SECONDS);
    }

4. 整合Quartz

Quartz是一个完全由Java编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制,要理解它的使用方式,需要先理解它的几个核心概念:

  1. Job: 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:

void execute(JobExecutionContext context)

  1. JobDetail: 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
  2. Trigger: 代表一个调度参数的配置,什么时候去调。
  3. Scheduler: 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。

有了这些概念之后,我们就可以把Quartz整合到我们的springboot项目中了。

  1. 引入quartz依赖
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
  1. 配置
@Configuration
public class QuartzConfig {
    @Bean
    public JobDetail quartzDetail(){
        return JobBuilder.newJob(QuartzTest.class).withIdentity("QuartzTest").storeDurably().build();
    }
    @Bean
    public SimpleTrigger quartzTrigger(){
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(10)
                .repeatForever();
        return TriggerBuilder.newTrigger().forJob(quartzDetail())
                .withIdentity("QuartzTest")
                .withSchedule(scheduleBuilder)
                .build();
    }
}
  1. 测试
public class QuartzTest extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext){
        System.out.println("quartz执行一次定时任务 ");
    }
}

5. 使用Scheduled注解

@Scheduled是spring为定时任务而生的一个注解,查看注解的源码:

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
//cron表达式
    String cron() default "";
//接收一个java.util.TimeZone#ID。
    String zone() default "";
//上一次执行完毕时间点之后多长时间再执行
    long fixedDelay() default -1;
//支持占位符形式的字符串类型的fixedDelay
    String fixedDelayString() default "";
//上一次开始执行时间点之后多长时间再执行
    long fixedRate() default -1;
//支持占位符形式的字符串类型的fixedRateString
    String fixedRateString() default "";
//第一次延迟多长时间后再执行    
    long initialDelay() default -1;
//支持占位符形式的字符串类型的initialDelay
    String initialDelayString() default "";
}

可以看出:Scheduled注解中的参数用来设置“定时”动作,通常情况下,比较常用的参数是cron(),这意味着我们需要学会一些cron表达式相关的语法,但由于内容较多,篇幅较长,在这里暂不铺开讲解,我们把cron语法相关放到文章最后,在此先讲解如何用Scheduled注解来实现定时任务。

  1. 开启定时任务支持
@SpringBootApplication
/**
 * 开启定时任务支持
 */
@EnableScheduling
public class TestApplication  extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(this.getClass());
    }
}
  1. 使用
@Component
public class ScheduledTest {
    private Logger logger = LoggerFactory.getLogger(ScheduledTest.class);
    /**
     * 每15秒执行一次定时任务
     */
    @Scheduled(cron = "0/15 * * * * ? ")
    public void testCron(){
        logger.info("Scheduled 执行一次定时任务");
    }
}

6. cron表达式

cron表达式是一个字符串其语法为:

[秒] [分] [小时] [日] [月] [周] [年]

其中[年]为非必填项,因此通常cron表达式通常由6或7部分内容组成,内容的取值为数字或者一些cron表达式约定的特殊字符,这些特殊字符称为“通配符”,每一个通配符分别代指一种值。cron表达式可以用这样的表格来表示:

顺序取值范围特殊字符串范围
0~60, - * /
0-60, - * /
0-23, - * /
1-31, - * /
1-12 / JAN-DEC, - * ? / L W
1-7 / SUN-SAT, - * ? / L #
年(可省略)1970-2099, - * /

其中通配符的解释以及作用如下:

通配符代表的值解释
*所有值如:时字段为*,代表每小时都触发
?不指定值如:周字段为?,代表表达式不关心是周几
-区间如:时字段设置2-5,代表2,3,4,5点钟时都触发
,多个值如:时字段设置2,3,5,代表2,3,5点都会触发
/递增值如:时字段设置0/2,代表每两个小时触发,时字段设置 2/5,代表从2时开始每隔5小时触发一次
L最后值如:日字段设置L,代表本月最后一天
W最近工作日如:在日字段设置13W,代表没约13日最近的那个工作日触发一次
#序号如:在周字段设置5#2,代表每月的第二个周五
示例:
每2秒执行一次:0/5 ?
每5分钟执行一次:0 0/5 * ?
1分、12分、45分执行一次:0 1,12,45 * ?
每天23点59分59秒执行一次:59 59 23 ?
每月15号凌晨3点执行一次:0 0 3 15 * ?
每月最后一天12点执行一次:0 0 12 L * ?

觉得本文对你有帮助?请分享给更多人
关注「编程无界」,提升装逼技能

查看原文

jaybril 发布了文章 · 2019-02-13

SpringBoot下使用定时任务的方式全揭秘

本文旨在用通俗的语言讲述枯燥的知识

定时任务作为一种系统调度工具,在一些需要有定时作业的系统中应用广泛,如每逢某个时间点统计数据、在将来某个时刻执行某些动作...定时任务在主流开发语言均提供相应的API供开发者调用,在Java中,实现定时任务有很多种方式,原生的方式实现一个完整定时任务需要由Timer、TimerTask两个类,Timer是定时器类,用来按计划开启后台线程执行指定任务,TimerTask一个抽象类,它的子类代表一个可以被Timer计划的任务。除此之外,还可以用ScheduledExecutorService类或者使用第三方jar库Quartz,其中Quartz是一个优秀的定时任务框架,发展至今已经非常成熟,以致后来其他的定时任务框架的核心思想或底层大多源于Quartz。

springboot作为Java的一种开发框架,在springboot项目中实现定时任务不仅可以使用Java提供的原生方式,还可以使用springboot提供的定时任务API,下面,小编把Java原生和springboot所有的实现定时任务的方式做一个整合。

文章提纲:
1、使用线程
2、使用Timer类
3、使用ScheduledExecutorService类
4、使用Quartz
5、使用spring的@Scheduled注解
6、cron表达式

1. 线程实现

利用线程可以设定休眠时间的方式可以实现简单的定时任务逻辑。

    public static void main(String[] args){
        //定时任务间隔时间
        int sleepTime=2*1000;
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        System.out.println("Thread方式执行一次定时任务");
                        //线程休眠规定时间
                        Thread.sleep(sleepTime);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

2. Timer类

Timer类允许调度一个TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行。

    public static void main(String[] args){
        int sleepTime=2*1000;
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("Timer方式执行一次定时任务");
            }
        };
        new Timer().schedule(timerTask,1,sleepTime);
    }

3. ScheduledExecutorService类

ScheduledExecutorService,是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。
因此,基于ScheduledExecutorService类的定时任务类,归根到底也是基于线程的调度实现的。

    public static void main(String[] args){
        int sleepTime=2*1000;
        ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
        scheduledExecutor.scheduleAtFixedRate(
                new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("ScheduledExecutorService方式执行一次定时任务");
                    }
                }
        ,1,sleepTime, TimeUnit.SECONDS);
    }

4. 整合Quartz

Quartz是一个完全由Java编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制,要理解它的使用方式,需要先理解它的几个核心概念:

  1. Job: 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:

void execute(JobExecutionContext context)

  1. JobDetail: 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
  2. Trigger: 代表一个调度参数的配置,什么时候去调。
  3. Scheduler: 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。

有了这些概念之后,我们就可以把Quartz整合到我们的springboot项目中了。

  1. 引入quartz依赖
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
  1. 配置
@Configuration
public class QuartzConfig {
    @Bean
    public JobDetail quartzDetail(){
        return JobBuilder.newJob(QuartzTest.class).withIdentity("QuartzTest").storeDurably().build();
    }
    @Bean
    public SimpleTrigger quartzTrigger(){
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(10)
                .repeatForever();
        return TriggerBuilder.newTrigger().forJob(quartzDetail())
                .withIdentity("QuartzTest")
                .withSchedule(scheduleBuilder)
                .build();
    }
}
  1. 测试
public class QuartzTest extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext){
        System.out.println("quartz执行一次定时任务 ");
    }
}

5. 使用Scheduled注解

@Scheduled是spring为定时任务而生的一个注解,查看注解的源码:

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
//cron表达式
    String cron() default "";
//接收一个java.util.TimeZone#ID。
    String zone() default "";
//上一次执行完毕时间点之后多长时间再执行
    long fixedDelay() default -1;
//支持占位符形式的字符串类型的fixedDelay
    String fixedDelayString() default "";
//上一次开始执行时间点之后多长时间再执行
    long fixedRate() default -1;
//支持占位符形式的字符串类型的fixedRateString
    String fixedRateString() default "";
//第一次延迟多长时间后再执行    
    long initialDelay() default -1;
//支持占位符形式的字符串类型的initialDelay
    String initialDelayString() default "";
}

可以看出:Scheduled注解中的参数用来设置“定时”动作,通常情况下,比较常用的参数是cron(),这意味着我们需要学会一些cron表达式相关的语法,但由于内容较多,篇幅较长,在这里暂不铺开讲解,我们把cron语法相关放到文章最后,在此先讲解如何用Scheduled注解来实现定时任务。

  1. 开启定时任务支持
@SpringBootApplication
/**
 * 开启定时任务支持
 */
@EnableScheduling
public class TestApplication  extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(this.getClass());
    }
}
  1. 使用
@Component
public class ScheduledTest {
    private Logger logger = LoggerFactory.getLogger(ScheduledTest.class);
    /**
     * 每15秒执行一次定时任务
     */
    @Scheduled(cron = "0/15 * * * * ? ")
    public void testCron(){
        logger.info("Scheduled 执行一次定时任务");
    }
}

6. cron表达式

cron表达式是一个字符串其语法为:

[秒] [分] [小时] [日] [月] [周] [年]

其中[年]为非必填项,因此通常cron表达式通常由6或7部分内容组成,内容的取值为数字或者一些cron表达式约定的特殊字符,这些特殊字符称为“通配符”,每一个通配符分别代指一种值。cron表达式可以用这样的表格来表示:

顺序取值范围特殊字符串范围
0~60, - * /
0-60, - * /
0-23, - * /
1-31, - * /
1-12 / JAN-DEC, - * ? / L W
1-7 / SUN-SAT, - * ? / L #
年(可省略)1970-2099, - * /

其中通配符的解释以及作用如下:

通配符代表的值解释
*所有值如:时字段为*,代表每小时都触发
?不指定值如:周字段为?,代表表达式不关心是周几
-区间如:时字段设置2-5,代表2,3,4,5点钟时都触发
,多个值如:时字段设置2,3,5,代表2,3,5点都会触发
/递增值如:时字段设置0/2,代表每两个小时触发,时字段设置 2/5,代表从2时开始每隔5小时触发一次
L最后值如:日字段设置L,代表本月最后一天
W最近工作日如:在日字段设置13W,代表没约13日最近的那个工作日触发一次
#序号如:在周字段设置5#2,代表每月的第二个周五
示例:
每2秒执行一次:0/5 ?
每5分钟执行一次:0 0/5 * ?
1分、12分、45分执行一次:0 1,12,45 * ?
每天23点59分59秒执行一次:59 59 23 ?
每月15号凌晨3点执行一次:0 0 3 15 * ?
每月最后一天12点执行一次:0 0 12 L * ?

觉得本文对你有帮助?请分享给更多人
关注「编程无界」,提升装逼技能

查看原文

赞 23 收藏 17 评论 0

jaybril 收藏了文章 · 2019-01-21

在docker中用Tomcat运行web项目

本文旨在用最通俗的语言讲述最枯燥的基本知识

上一篇文章《为什么要用docker》已经讲述了什么是docker以及我们要用docker的原因,并且讲解了如何安装docker。这时候很多读者磨拳擦脚跃跃欲试但却发现安装好docker之后就无从下手了,那么,接下来,小编会从以下方面讲述docker的一些基础知识,当然,理论都是生硬的,所以小编选取了javaweb项目中最常用的一个软件--tomcat的安装和使用来引导学习一些docker相关的知识,借此让读者能够从实战的角度去理解docker为什么会有这些基础知识以及如何使用这些基础知识。

我们知道,web开发和部署离不开tomcat,而在目前的实际情况是:
一个项目中,每个开发者本机电脑都有自己的tomcat或者用idea的内置tomcat,当我们开发完成提交测试时,测试服务器上也有一个tomcat;当项目上线时,线上服务器也有一个tomcat,因此就很容易出现一些奇奇怪怪的问题,比如在同事A电脑上能正常运行的,到同事B电脑上就出问题的;或者是在测试环境里一切正常,到了线上bug一堆。这时候docker就有了用武之地,项目负责人把docker的tomcat镜像做好了之后上传到镜像仓库,项目成员的电脑环境、测试环境、线上环境均拉取这个tomcat使用,就能保持在所有的环境下tomcat的版本、设置都是一致的,避免了一些非技术的问题。
那么,现在我们就用docker来做一个tomcat的环境。

  1. 镜像的查找和拉取

首先,我们需要去镜像仓库找到我们要的镜像,查找镜像的语法是

docker search 镜像名称:镜像TAG

因此,查找Tomcat镜像:

docker search tomcat

有读者会疑问:什么是镜像TAG?
镜像的TAG值就是镜像的版本,就比如Tomcat有7.0、8.5、9.0等我们常用的版本,那么相应的这些软件被做成docker镜像之后,官方也会根据软件本身的版本对应出docker镜像的版本。
下图为查找结果:
docker下查找Tomcat镜像
可以看到,有很多的Tomcat镜像,那么我们应该怎么选用这些镜像呢?通常情况下,我们都知道官方的东西基本上代表安全无公害,因为可以看到右边有official标识为OK的就是官方的镜像,因此我们拉取第一个Tomcat就可以。
拉取镜像的语法是:

docker pull 镜像NAME:镜像TAG

因为我们要拉取Tomcat时,就可以这样写:

docker pull tomcat

有人说你怎么没加上镜像TAG
在拉取镜像时,如果加上镜像TAG,就会去查找是否有相应版本的镜像,如果有则拉取,如果没有则不拉去。而没有加TAG时,就默认代表拉取的是该镜像的最新版本。

假如需要拉取固定版本如拉取Tomcat8.5:

docker pull tomcat:8.5

就会相应的拉取了该版本的Tomcat镜像。
拉取成功之后,我们要查看我们拉取的版本,可以用命名:

docker images

可以看到,镜像列表里已经有了我们要拉取的Tomcat镜像
本机的镜像列表

  1. 镜像的使用

镜像拉取下来之后,就如我们把从网上把下载到了本地电脑,但是没有任何生命的迹象,只有把它安装运行起来,才是一个活的容器,相应地,docker镜像只有在run起来的时候,才是一个有生命的容器。
创建和运行一个Tomcat容器只需要一行命令:

 
docker run  --name my_tomcat tomcat:8.5

其中 --name是指给容器起的名字,后面的tomcat是指下载的惊喜的NAME。8.5是镜像TAG。
到这里,一个tomcat服务就安装并且启动好了,因为默认的Tomcat端口都是8080,因此通常情况下此时我们用ip:8080就应该可以访问到Tomcat的标志性主页了。
但是!!!
事实并不是如此
因为我们是在docker下运行的容器,你们的脑海中可否还有docker的logo图?
大海就相当于我们的服务器(宿主机),而在海中游走的鲸鱼就是docker服务,而鲸鱼上的每一个集装箱都是一个容器,也就是说:
容器之间是互相隔离的,容器和宿主服务器也是互相隔离的。
因此我们在docker中运行了一个Tomcat的服务器,虽然端口是8080,
但是~
那是容器中的8080端口,因此我们通过ip:8080访问到的其实只是宿主机上的8080端口,并不是容器中的端口。
此时我们只需要把容器中的端口映射到宿主机上相应的端口即可。
端口映射只需要在运行时加入指令 -p 映射的宿主机端口:容器运行端口
如下,把容器运行的8080端口映射到宿主机的8099端口:
名称设置为my_tomcat_1

docker run -p 8099:8080  --name my_tomcat_1 tomcat:8.5

此时,你会发现,控制台打出了Tomcat启动的日志,启动完成后,我们在浏览器上用ip:8099访问,就会发现,Tomcat主页就显示了,说明Tomcat已经正确地运行起来了。
而用命令也可以查看容器是否起来,我们用docker ps 能查出本docker中运行的多有容器:

docker ps

在Tomcat运行的情况下,当你用ctrl+c回到命令行输入docker ps时,发现并没有找到my_tomcat_1这个Tomcat容器,而你重新用ip:8099发现Tomcat也不见了
噢 no!
什么原因?
这是因为:在Linux下用指令的方式运行程序,如果没有加后台执行的指令,那么在切回到命令时,程序就被杀死了。
因此,此时是因为我们运行Tomcat容器时,没有设置在后台运行,因此,需要在运行时,加上后台运行的指令,在docker中设置后台运行只需要在run命名中加入-d即可。
因此我们重新运行Tomcat,名称设置为my_tomcat_2:

docker run -d -p 8099:8080  --name my_tomcat_2 tomcat:8.5

运行完成后,此时我们放心大胆的回到命名行。重新执行docker ps
看控制台的输出:
展示正在运行的所有容器
可以看到:my_tomcat_2已经在运行,IMAGE代表所用镜像以及镜像版本,PORTS显示的是0.0.0.0:8099->8080/tcp 。代表的是把本机的8080端口映射到宿主机上的8099端口中。

但是,有读者又有疑问了:
”Tomcat是运行了,那我怎么把我的项目丢进去docker的Tomcat中呢?我用FTP工具链接到服务器上也找不到Tomcat在哪里,怎么办?”

上面提到,docker中容器和宿主机是互相隔离的,因此容器是不会在宿主机中有明确的文件夹路径,所以找不到才是对的。
但是既然端口都能映射,为啥文件夹不能映射呢?
在docker中,可以用 -v 指令指定把容器中的某个文件夹挂载到宿主机中
它的语法为:

-v 宿主机目录:容器目录

所以我们可以在创建运行一个容器时,同时可以把指定的文件夹挂载到宿主机中
通常情况下,Tomcat运行程序的文件是在webapps下的,那么可以在运行时把这个文件夹挂载到宿主机某个路径上(如:/data目录中),
名称设置为my_tomcat_3,端口设置为8098,要把容器中的webapps文件夹挂载到宿主机中/data/my_tomcat_3/webapps文件夹:

docker run -d -p 8098:8080 -v /data/my_tomcat_3/webapps:/usr/local/tomcat/webapps  --name my_tomcat_3  tomcat:8.5

此时我们再用FTP工具链接到宿主机服务器上,进入data文件夹,就会发现:my_tomcat_3/webapps文件夹已经躺在里边了,可见已经挂载成功。

把文件夹挂载成功之后,此时我们把web项目丢到宿主机中的webapps下,重启Tomcat容器后,再用ip:8099访问,就会展示我们的web项目的内容了。
到这里,docker中用Tomcat运行web项目的工作已经完成。

但是,回过头来想想,留下很多问题:

  1. docker run是创建并且运行容器,那我怎么控制容器的状态呢?比如启动和停止
  1. 为什么我在docker run时一直在重命名Tomcat的名字my_tomcat_1、my_tomcat_2、my_tomcat_3..
  2. 拉取和运行了那么多镜像,占据很多磁盘空间,怎么处理?
  3. 运行后的Tomcat,怎么查看日志?
  4. 我想要修改一下Tomcat中的server.xml的设置,但是宿主机中有没有这个文件,难道我要挂载出来吗?

看完此文之后,你有什么问题吗?欢迎读者把问题后台留言给小编或者加小编的微信,下一篇文章,小编会针对上面的问题以及读者提出的问题,做一个完整的解答以及把如何做好的Tomcat上传到镜像仓库做一个演示,所以:
关!注!我!

下期小编甚至会把常用的一些docker命名整理出来,方便大家集中记忆和使用!


觉得本文对你有帮助?请分享给更多人
关注「编程无界」,提升装逼技能

查看原文

jaybril 发布了文章 · 2019-01-21

在docker中用Tomcat运行web项目

本文旨在用最通俗的语言讲述最枯燥的基本知识

上一篇文章《为什么要用docker》已经讲述了什么是docker以及我们要用docker的原因,并且讲解了如何安装docker。这时候很多读者磨拳擦脚跃跃欲试但却发现安装好docker之后就无从下手了,那么,接下来,小编会从以下方面讲述docker的一些基础知识,当然,理论都是生硬的,所以小编选取了javaweb项目中最常用的一个软件--tomcat的安装和使用来引导学习一些docker相关的知识,借此让读者能够从实战的角度去理解docker为什么会有这些基础知识以及如何使用这些基础知识。

我们知道,web开发和部署离不开tomcat,而在目前的实际情况是:
一个项目中,每个开发者本机电脑都有自己的tomcat或者用idea的内置tomcat,当我们开发完成提交测试时,测试服务器上也有一个tomcat;当项目上线时,线上服务器也有一个tomcat,因此就很容易出现一些奇奇怪怪的问题,比如在同事A电脑上能正常运行的,到同事B电脑上就出问题的;或者是在测试环境里一切正常,到了线上bug一堆。这时候docker就有了用武之地,项目负责人把docker的tomcat镜像做好了之后上传到镜像仓库,项目成员的电脑环境、测试环境、线上环境均拉取这个tomcat使用,就能保持在所有的环境下tomcat的版本、设置都是一致的,避免了一些非技术的问题。
那么,现在我们就用docker来做一个tomcat的环境。

  1. 镜像的查找和拉取

首先,我们需要去镜像仓库找到我们要的镜像,查找镜像的语法是

docker search 镜像名称:镜像TAG

因此,查找Tomcat镜像:

docker search tomcat

有读者会疑问:什么是镜像TAG?
镜像的TAG值就是镜像的版本,就比如Tomcat有7.0、8.5、9.0等我们常用的版本,那么相应的这些软件被做成docker镜像之后,官方也会根据软件本身的版本对应出docker镜像的版本。
下图为查找结果:
docker下查找Tomcat镜像
可以看到,有很多的Tomcat镜像,那么我们应该怎么选用这些镜像呢?通常情况下,我们都知道官方的东西基本上代表安全无公害,因为可以看到右边有official标识为OK的就是官方的镜像,因此我们拉取第一个Tomcat就可以。
拉取镜像的语法是:

docker pull 镜像NAME:镜像TAG

因为我们要拉取Tomcat时,就可以这样写:

docker pull tomcat

有人说你怎么没加上镜像TAG
在拉取镜像时,如果加上镜像TAG,就会去查找是否有相应版本的镜像,如果有则拉取,如果没有则不拉去。而没有加TAG时,就默认代表拉取的是该镜像的最新版本。

假如需要拉取固定版本如拉取Tomcat8.5:

docker pull tomcat:8.5

就会相应的拉取了该版本的Tomcat镜像。
拉取成功之后,我们要查看我们拉取的版本,可以用命名:

docker images

可以看到,镜像列表里已经有了我们要拉取的Tomcat镜像
本机的镜像列表

  1. 镜像的使用

镜像拉取下来之后,就如我们把从网上把下载到了本地电脑,但是没有任何生命的迹象,只有把它安装运行起来,才是一个活的容器,相应地,docker镜像只有在run起来的时候,才是一个有生命的容器。
创建和运行一个Tomcat容器只需要一行命令:

 
docker run  --name my_tomcat tomcat:8.5

其中 --name是指给容器起的名字,后面的tomcat是指下载的惊喜的NAME。8.5是镜像TAG。
到这里,一个tomcat服务就安装并且启动好了,因为默认的Tomcat端口都是8080,因此通常情况下此时我们用ip:8080就应该可以访问到Tomcat的标志性主页了。
但是!!!
事实并不是如此
因为我们是在docker下运行的容器,你们的脑海中可否还有docker的logo图?
大海就相当于我们的服务器(宿主机),而在海中游走的鲸鱼就是docker服务,而鲸鱼上的每一个集装箱都是一个容器,也就是说:
容器之间是互相隔离的,容器和宿主服务器也是互相隔离的。
因此我们在docker中运行了一个Tomcat的服务器,虽然端口是8080,
但是~
那是容器中的8080端口,因此我们通过ip:8080访问到的其实只是宿主机上的8080端口,并不是容器中的端口。
此时我们只需要把容器中的端口映射到宿主机上相应的端口即可。
端口映射只需要在运行时加入指令 -p 映射的宿主机端口:容器运行端口
如下,把容器运行的8080端口映射到宿主机的8099端口:
名称设置为my_tomcat_1

docker run -p 8099:8080  --name my_tomcat_1 tomcat:8.5

此时,你会发现,控制台打出了Tomcat启动的日志,启动完成后,我们在浏览器上用ip:8099访问,就会发现,Tomcat主页就显示了,说明Tomcat已经正确地运行起来了。
而用命令也可以查看容器是否起来,我们用docker ps 能查出本docker中运行的多有容器:

docker ps

在Tomcat运行的情况下,当你用ctrl+c回到命令行输入docker ps时,发现并没有找到my_tomcat_1这个Tomcat容器,而你重新用ip:8099发现Tomcat也不见了
噢 no!
什么原因?
这是因为:在Linux下用指令的方式运行程序,如果没有加后台执行的指令,那么在切回到命令时,程序就被杀死了。
因此,此时是因为我们运行Tomcat容器时,没有设置在后台运行,因此,需要在运行时,加上后台运行的指令,在docker中设置后台运行只需要在run命名中加入-d即可。
因此我们重新运行Tomcat,名称设置为my_tomcat_2:

docker run -d -p 8099:8080  --name my_tomcat_2 tomcat:8.5

运行完成后,此时我们放心大胆的回到命名行。重新执行docker ps
看控制台的输出:
展示正在运行的所有容器
可以看到:my_tomcat_2已经在运行,IMAGE代表所用镜像以及镜像版本,PORTS显示的是0.0.0.0:8099->8080/tcp 。代表的是把本机的8080端口映射到宿主机上的8099端口中。

但是,有读者又有疑问了:
”Tomcat是运行了,那我怎么把我的项目丢进去docker的Tomcat中呢?我用FTP工具链接到服务器上也找不到Tomcat在哪里,怎么办?”

上面提到,docker中容器和宿主机是互相隔离的,因此容器是不会在宿主机中有明确的文件夹路径,所以找不到才是对的。
但是既然端口都能映射,为啥文件夹不能映射呢?
在docker中,可以用 -v 指令指定把容器中的某个文件夹挂载到宿主机中
它的语法为:

-v 宿主机目录:容器目录

所以我们可以在创建运行一个容器时,同时可以把指定的文件夹挂载到宿主机中
通常情况下,Tomcat运行程序的文件是在webapps下的,那么可以在运行时把这个文件夹挂载到宿主机某个路径上(如:/data目录中),
名称设置为my_tomcat_3,端口设置为8098,要把容器中的webapps文件夹挂载到宿主机中/data/my_tomcat_3/webapps文件夹:

docker run -d -p 8098:8080 -v /data/my_tomcat_3/webapps:/usr/local/tomcat/webapps  --name my_tomcat_3  tomcat:8.5

此时我们再用FTP工具链接到宿主机服务器上,进入data文件夹,就会发现:my_tomcat_3/webapps文件夹已经躺在里边了,可见已经挂载成功。

把文件夹挂载成功之后,此时我们把web项目丢到宿主机中的webapps下,重启Tomcat容器后,再用ip:8099访问,就会展示我们的web项目的内容了。
到这里,docker中用Tomcat运行web项目的工作已经完成。

但是,回过头来想想,留下很多问题:

  1. docker run是创建并且运行容器,那我怎么控制容器的状态呢?比如启动和停止
  1. 为什么我在docker run时一直在重命名Tomcat的名字my_tomcat_1、my_tomcat_2、my_tomcat_3..
  2. 拉取和运行了那么多镜像,占据很多磁盘空间,怎么处理?
  3. 运行后的Tomcat,怎么查看日志?
  4. 我想要修改一下Tomcat中的server.xml的设置,但是宿主机中有没有这个文件,难道我要挂载出来吗?

看完此文之后,你有什么问题吗?欢迎读者把问题后台留言给小编或者加小编的微信,下一篇文章,小编会针对上面的问题以及读者提出的问题,做一个完整的解答以及把如何做好的Tomcat上传到镜像仓库做一个演示,所以:
关!注!我!

下期小编甚至会把常用的一些docker命名整理出来,方便大家集中记忆和使用!


觉得本文对你有帮助?请分享给更多人
关注「编程无界」,提升装逼技能

查看原文

赞 7 收藏 6 评论 0

认证与成就

  • 获得 619 次点赞
  • 获得 5 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 5 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2014-12-25
个人主页被 1.1k 人浏览