使用docker自定义一个nginx+mysql+jdk的镜像


我想做一个小项目,一个基于websocket的B端聊天室,需要的环境有:

mysql,jdk,nginx

我的想法是这样:springboot写的后端服务,前端web的页面就不放进去了,在nginx下运行,因为之前学springboot的时候感觉懵懵的,分开写好一点。其实不怕麻烦的话我是打算把mysql,jdk,nginx完全分开,做成3个容器,但是考虑了诸多因素之后还是决定做一个“一劳永逸“的镜像或者dockerfile

准备文件:

1.jdk-linux-x64.tar.gz(jdk1.8,解压后大概350MB,如果不打算装jdk就可以跳过了)

2.my_path.sh(设置环境变量的脚本,暂时没有用到)

#!/bin/bash
export JAVA_HOME=/usr/local/java/jdk1.8.0_131
export JRE_HOME=$JAVA_HOME/jre
export PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin

3.authorized_keys和sshd_config(公钥文件和sshd配置文件,这里提示一下,公钥文件除了所属用户外不能有写权限,否则不能用,这是我踩的坑,我samba共享了一个路径,路径指向一个ntfs格式的硬盘,但是这个硬盘的权限有问题,所有文件都是777)

sshd_config其实也只改了两行,允许root登录,并且允许用密钥登录,按照我的习惯还应该禁止密码登录,不过随便啦,docker的root密码就没初始化过。

PermitRootLogin yes
PubkeyAuthentication yes

4.mysql.cnf(这个是我当时在centos下用mysql时的配置文件,文件编码全都改成utf8)

[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
character-set-server=utf8

datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock

symbolic-links=0

log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

5.mysqld.cnf(这是直接从安装过一次的系统里面提取出来的,唯一改的是bind_address,原本的是127.0.0.1,远程是连接不到的)

[mysqld_safe]
socket          = /var/run/mysqld/mysqld.sock
nice            = 0

[mysqld]
#
# * Basic Settings
#
user            = mysql
pid-file        = /var/run/mysqld/mysqld.pid
socket          = /var/run/mysqld/mysqld.sock
port            = 3306
basedir         = /usr
datadir         = /var/lib/mysql
tmpdir          = /tmp
lc-messages-dir = /usr/share/mysql
skip-external-locking
#
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
bind-address            = 0.0.0.0

key_buffer_size         = 16M
max_allowed_packet      = 16M
thread_stack            = 192K
thread_cache_size       = 8

myisam-recover-options  = BACKUP

query_cache_limit       = 1M
query_cache_size        = 16M

log_error = /var/log/mysql/error.log

expire_logs_days        = 10
max_binlog_size   = 100M

6.init.sh

#!/bin/bash

service nginx start #web服务正常启动
service ssh start #ssh服务正常启动

usermod -d /var/lib/mysql/ mysql
ln -s /var/lib/mysql/mysql.sock /tmp/mysql.sock
chown -R mysql:mysql /var/lib/mysql  #这三行解决No directory, logging in with HOME=/的问题
service mysql start #mysqlserver正常启动

mysql -u root < /init.sql #执行sql文件的脚本,因为root用户默认没有密码,所以可以这么搞

service mysql restart #mysqlserver重启

rm -f init.sql #卸磨杀驴

7.init.sql(修改root密码,修改数据库的默认用户为root,允许root通过任意地址登录)

USE mysql;

GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;
UPDATE user SET authentication_string=password('123456') WHERE user='root' AND host ='localhost';
UPDATE user SET plugin="mysql_native_password"; 
flush PRIVILEGES;

8.dockerfile(书写要配置的镜像内容)

FROM ubuntu:18.04
MAINTAINER thankvinci "thankvinci@163.com"
COPY authorized_keys /root/.ssh/authorized_keys #将公钥文件复制到对应目录
COPY sshd_config /etc/ssh/sshd_config #复制sshd配置文件
COPY jdk1.8.0_131 /usr/local/java/jdk1.8.0_131 #复制jdk
COPY my_path.sh /etc/profile.d/my_path.sh #复制自定义环境变量
COPY init.sql /init.sql #将初始化mysql的sql脚本放到根目录
COPY init.sh /init.sh #将进行初始化的脚本放到根目录
COPY start.sh /start.sh #最后一次修改时添加
RUN apt-get update \ 
	&& apt-get install -y openssh-server \
	&& apt-get install -y mysql-server-5.7 \  
		#通过apt安装的mysql下面的默认用户不是root,而是debian-sys-maint,密码保存在/etc/mysql/debian.cnf里面,不过经过我的测试不用理会这个默认用户,按照下面的操作就行
	&& apt-get install -y nginx \	
	&& apt-get install -y lsof \
	&& apt-get install -y vim \
	&& apt-get install -y iproute2 
	
COPY mysql.cnf /etc/mysql/conf.d/mysql.cnf #覆盖原来的配置文件,改编码为utf8
COPY mysqld.cnf /etc/mysql/mysql.conf.d/mysqld.cnf #修改bind_address为0.0.0.0,这样才可以远程访问
	
ENV LANG=C.UTF-8 #语言环境

EXPOSE 22 80 3306 8080 #声明要映射的端口,没啥用,我比较常用的还是socat
CMD /bin/bash -c "/start.sh && /bin/bash" #最后一次修改时添加的

然后进行镜像构建

docker build -t ubuntu:runenv .

构建完成:

Successfully built b19c9cdf2132
Successfully tagged ubuntu:runenv
thankvinci-desktop# docker images
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
ubuntu       runenv    b19c9cdf2132   2 minutes ago   969MB

3MB/s的小水管真的难受,最终得到的镜像是969MB,近1个G,挺大的,而且后端服务还没搭上去,前端页面也没放进去,甚至数据库还没有建表。

如果我要用socat,那么运行前,我得先建立一个网络,这样我才能定这个容器的IP地址,当然现在只是测试,而且有端口映射就不用知道容器的IP,所以网络这一步就省了。

docker network create --driver bridge --subnet=172.18.12.0/16 --gateway=172.18.1.1 myprojectnet

接下来运行一下试试

docker run --name runenv -it ubuntu:runenv /bin/bash

init初始化脚本运行结果是:mysql编码,root密码,默认用户 都修改成功了,但是source /etc/profile的内容却不生效,仔细研究了一下,因为这个source是在脚本内的,所以$PATH也是更新在脚本的shell,与父进程无关,如果非要它实现的话,目前只找到一个方法,那就是source init脚本。

接着我又想到如果我关闭容器后要重新开启这些服务呢?

我有两个思路,一个是在init脚本初始化结束后写一个启动服务脚本,然后清空init脚本,在init脚本中执行启动服务脚本,

另一个和这个一样,但不同的是要修改镜像内容,当也就是容器启动CMD,这样不加执行参数就是默认执行初始化,


写到这里后我发现一件事情,就是我根本就没必要把jdk加载到全局PATH里面,我可以之后再写一个启动服务的脚本,所以别太复杂化。

这里插一下docker启动容器的一些特点吧,首先是CMD也就是容器启动时运行的程序,这里分为3种情况,第1种是,当docker run 的参数为没有d时,容器启动后会直接进容器,当从容器出来时,容器就停止了,第2种是,当docker run的参数有d时,容器是后台运行的,使用exec进入容器后再出来容器还在运行,第3种是,当CMD为一个脚本时,脚本执行完容器就关闭了,不论是启动参数有没有d,而通常情况下CMD都是/bin/bash,也就是相当于一个shell进程一直在运行,如果参数没有d的话容器停止后再启动,其实是放在后台运行,相当于加了d,再通过exec进入容器就和上面第二点一样了。


根据在菜鸟上看到的dockerfile文件中CMD只能有一条是生效的,像下面这种

CMD /init.sh && /bin/bash

执行完init.sh,容器就退出了,/bin/bash变成了在物理机执行,但是,也不是说不能执行两条指令,使用-c 参数,把后面的字符串解析成一条命令去执行,就像这样

CMD /bin/bash -c "/init.sh && /bin/bash"

最终

最后再做亿次改动

1.多出来一个start.sh,就不要用init.sh去生成了

#!/bin/bash

if [ -f init.sh ]
then
/init.sh
echo "初始化镜像服务完成"
rm -f init.sh #卸磨杀驴
else
service nginx start
service ssh start
service mysql start
fi

2.CMD跑的指令也改成这样,当然copy的内容也多了一条

COPY start.sh /start.sh
CMD /bin/bash -c "/start.sh && /bin/bash"

再次构建后启动

容器启动,映射我要的全部端口

docker run --name runenv -p 80:80 -p 3306:3306 -p 8080:8080 -p 10022:22 -itd ubuntu:runenv
###强烈建议用--name参数去命名重要的容器,因为如果要启停容器自己就知道容器名,不用去docker ps找,比较方便,然后docker容器名是唯一的,不能命名冲突
##-p指定端口映射,左边是物理机的端口,右边是容器的端口
#-itd,其实-d就可以,d表示容器启动后搁后台运行

用docker ps查看可以看到

thankvinci-desktop# docker ps
CONTAINER ID   IMAGE           COMMAND                  CREATED          STATUS          PORTS                                                                                                                                                              NAMES
1605c64bf987   ubuntu:runenv   "/bin/sh -c '/bin/ba…"   15 seconds ago   Up 13 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp, 0.0.0.0:10022->22/tcp, :::10022->22/tcp   runenv

实际上用navicat,浏览器,putty都能访问现有的三个服务

关于容器的启停

docker start 1605c64bf987 #启动容器,容器ID
docker start runenv #启动容器,容器名
#停止是stop,重启是restart,主要是如果忘了机器开着容器我就关机了,那么重新开机后这个容器是处于关闭的状态的,要start,这个时候知道容器名就不用去找了,很方便。

关于容器的进出

docker exec -it runenv /bin/bash #runenv为容器名
#从容器中退出只需要输入exit就可以,如果容器启动时有用-d参数,那么可以直接用这个指令进入容器
#如果容器启动时没有用-d参数,exit后就停止了,需要start后再exec进入。
#通过exec进入后的容器,exit后不会停止,不论他原来run的时候有没有-d参数。

我本来是以为说容器关闭后,再开启可能服务不会启动才写了else里面的内容,测试了一下,容器关闭前停止现有的三个服务,然后再次启动后,这三个服务都在运行中,也就是说else里面三个服务的启动很多余,里面可以酌情改成别的,比如说后续写的java后端打成jar包后就可以在else里面去执行,毕竟我也不想去把java的服务注册在系统里面。第一次启动容器时的步骤确实很重要,似乎得有第一次启动后面才会自动启动。

写到这里我想起把常用于服务端的socket通信端口给忘了😂,不过还没做到那一步,到时候再改改

最后注明一下:nginx的网站目录默认在/var/www/html,如果静态页面不用经常修改的话直接在dockerfile里面nginx安装后copy进去就行,如果和java后端一样需要经常修改的话,还是把三个服务分开装容器里好。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM