我想做一個小項目,一個基於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后端一樣需要經常修改的話,還是把三個服務分開裝容器里好。