1 编写Dockfile
首先我们先使用Dockerfile生成一个自己想要的镜像。
FROM ubuntu
MAINTAINER tyy<tyy@126.com>
# Time synchronization between host and container
#ENV TimeZone=Asia/Shanghai
#RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone > /etc/timezone
# copy Shanghai in host to /etc/localtime in container
#RUN sudo cp /usr/share/zoneinfo/$TimeZone ./
#COPY Shanghai /etc/localtime
#RUN rm Shanghai
# The above approach may not be successful on Linux systems
COPY Shanghai /etc/localtime
RUN apt-get update && apt-get install sudo
RUN sudo apt-get install -y vim
# net-tool include ifconfig,netstat so on...
RUN sudo apt-get install -y net-tools
RUN sudo apt-get install -y systemctl
# DEBIAN_FRONTEND=noninteractive means what yes/no is not appeared.
RUN DEBIAN_FRONTEND=noninteractive sudo apt-get install -y inetutils-ping
RUN DEBIAN_FRONTEND=noninteractive sudo apt-get install -y supervisor
RUN sudo apt-get install -y make
# install gcc,g++
RUN DEBIAN_FRONTEND=noninteractive sudo apt-get install -y build-essential
RUN DEBIAN_FRONTEND=noninteractive sudo apt-get install -y gcc
RUN DEBIAN_FRONTEND=noninteractive sudo apt-get install -y g++
EXPOSE 8893
CMD echo "success ok"
CMD /bin/bash
注意:
- 1)我在同步宿主机和容器的时间时,百度了看其他人的做法是使用软链接,但是我这里使用软链接没有成功,其它人也有可能出现这种情况,说明这种软链接的方法不是必定成功的。所以我这里手动先把/usr/share/zoneinfo/Asia/Shanghai文件拷贝到Dockfile所在目录下,然后执行COPY Shanghai /etc/localtime即可。
- 2)sudo apt-get install当你安装时,可能会出现yes/no这种需要与终端交互的情况,DEBIAN_FRONTEND=noninteractive就是表示我不交互,不加的话会报错。同理-y也需要加,不然可能也会出现其它错误。
2 构建
去到Dockerfile的目录下,执行下面的构建命令生成镜像。
sudo docker build -t myubuntu:1.0.0 .
如果你此时run一个容器,然后date查看时间,可以看到是与宿主机的时间一样的。
3 将镜像打包成文件
语法:docker save -o 要保存的文件名 要保存的镜像。
docker save -o myubuntu:1.0.0.tar myubuntu:1.0.0
4 将打包好的文件上传到Docker
上面打包镜像成文件后,我们经常会使用xftp进行拷贝到指定的生产环境的服务器上面,然后重新上传到Docker进行使用。
当然有些公司会直接将生成好的镜像push到阿里云上面,然后再从阿里云上面pull下来使用,这个在第6篇文章说过了,但是这种情况是针对有外网的情况下。
因为很多公司的生产环境下都是内网的,所以我们只能使用这种打包成文件的方法。
语法:
docker load --input 文件。
或者
docker load < 文件名。
例如:
# 假设已经将该压缩包上传到生产环境后,那么进行上传到Docker
sudo docker load < myubuntu:1.0.0.tar
可以看到,另一台服务器上面已经有这个镜像了。
5 创建容器并使用命令创建容器数据卷
本来我们是可以直接在Dockerfile中直接创建的,但是因为Dockerfile无法指定宿主机的路径,只能指定容器的路径,所以我这里喜欢使用命令去创建数据卷。
# -d表示后台执行,-it表示前台交互运行。--network host表示开放所有端口,可以使用-p来开放多个端口,例如-p8893:8893 -p8080:8080。
# 如果不开放所有端口,可能在docker想apt-get安装软件,可能会失败,即使能上外网。
# 并且注意,如果只给了-d而没有-it,那么如果此时容器没有前台进程,docker会自动把该容器停止掉。所以加上-it选项是有必要的。
# -v(volume)表示添加容器数据卷。privileged=true表示如果权限不够则给它权限。
sudo docker run -d -it --network host --name=docker_ubuntu_gw -v /宿主机绝对路径:/容器内目录 --privileged=true 镜像名。
# 看到容器创建成功
sudo docker ps
此时可以通过docker inspect 容器名去看是否挂载容器卷成功。
6 进入容器
此时,容器以及对应的容器卷以及创建完成,那么剩下的就是将程序拷贝到容器卷的目录下面,进行编译,编译成功后,那么就没问题了,当然这一步应该在公司就完成。
编译的时候我们应该进入容器编译,方便去适配docker里面的环境。进入容器的命令:
sudo docker exec -it 容器ID /bin/bash
然后后续缺少什么命令、软件的话,就继续安装,然后将myubuntu:1.0.0镜像不断更新即可。
7 容器内的时间与宿主机同步
如果在上面Dockerfile成功同步了,那么这一步就不需要再处理,这里只是写出来让大家知道也可以使用这种方法去同步,原理是一样的。
有时我们创建容器后,可能时间和宿主机是不一致的,所以我们需要进行统一。统一也很简单,就是将宿主机下面的/usr/share/zoneinfo/Asia/Shanghai拷贝到容器的/etc并改名成localtime即可。
# 个人建议使用方法三,比较安全,因为有时方法一二即使拷贝进容器成功,但是时间并未同步。
# 方法一:
# sudo docker cp /usr/share/zoneinfo/Asia/Shanghai 容器ID:/etc/localtime
# 上面可能报错,报错的话可以试试
# 方法二:
sudo docker cp /etc/localtime 容器ID:/etc/localtime
# 如果上面都不行,那么就将宿主机的文件先拷贝到容器卷目录,然后进入容器再改名成/etc/localtime即可。
# 方法三:
cp /usr/share/zoneinfo/Asia/Shanghai .
# 进入容器后的容器卷目录:
mv ./Shanghai /etc/localtime
如果你项目中没有程序开机自启的需求,那么下面就不需要再看。如果有则继续往下看。
8 使用docker-compose使容器开机自启
本小点的所有内容均是按照第5步的那个run命令来使用docker-compose创建容器的。
8.1 下载docker-compose
wget -c https://github.com/docker/compose/releases/download/v2.3.3/docker-compose-linux-x86_64
# 剪切到/usr/local/bin目录并改名为docker-compose,方便可以直接使用该命令
sudo mv docker-compose-linux-x86_64 /usr/local/bin/docker-compose
# 添加权限
cd /usr/local/bin
sudo chmod 777 docker-compose
8.2 编写项目文件docker-compose.yml
该文件建议放在项目目录下。
vim docker-compose.yml
添加内容:
# version代表这个docker-compose.yml的版本号
version: "3"
# services表示可以有多个应用服务,这个服务你可以理解为容器。
services:
# 服务PTZ-RTSP_URL-HK是我的程序名:
PTZ-RTSP_URL-HK:
container_name: docker_ubuntu_gw # 容器名,等价于--name
image: myubuntu:1.0.0 # 镜像名
restart: always # 设置开机启动
ports: # 宿主机与容器内的端口映射,规则为:宿主机端口:容器端口,该项是一个数组,可以添加多个映射。等价于-p。
- 8893:8893
volumes: # 容器卷,等价于-v。
- /xxx:/xxx
# 避免容器开启后自动退出
privileged: true
#interactive: true
tty: true
8.3 创建该容器
注意,因为我们在上面第5步的时候创建了一个名字为docker_ubuntu_gw 的容器,所以需要先把它删除掉,防止出错。没有就不需要删除。
# 先把第5步创建的容器名为docker_ubuntu_gw 的先删掉。
sudo docker rm -f 容器id
# 1. 使用docker-compose创建容器。
# 注意,必须要去到docker-compose.yml目录下执行,否则会报错。不想去该路径执行需要加上-f选项指定该yml文件的全部路径,例如-f xxx/docker-compose.yml。
# -d表示后台运行。
sudo docker-compose up -d
sudo docker-compose ps # 查看使用成功启动,看到STATUS为running表示成功,如果为RESTARTING为启动失败或者成功启动但自动退出了。
如果 docker-compose 版本比较低的话,STATUS显示为Up,说明正常启动。
8.4 测试开机后该容器是否会自启
# 1. 首先把该容器关掉。
# 注意不需要加上容器名或者容器ID。并且同样需要去到yml目录下执行,不去的话需要加上-f选项指定该yml文件的全部路径,例如-f xxx/docker-compose.yml。
docker-compose stop
# 2. 关机
sudo shutdown now
# 3. 重新开启该服务器(我的是虚拟机,所以方便开机关机)。
# 4. 查看是否开机自启。
docker-compose ps # 或者使用docker ps,但是还是建议统一使用docker-compose的命令,因为你的服务都已经交给docker-compose管理了,避免再使用docker命令,防止出现未知问题。
# 5. 进入该容器。
docker exec -it docker_ubuntu_gw /bin/bash
下面看到,容器开机自启成功,使用exec进入容器也是没有问题的。
9 为程序添加脚本,使程序伴随着容器开机自启
9.1 添加脚本文件并编写脚本
首先我们在上面的docker-compose.yml中额外添加多一个选项:即入口点entrypoint选项,以此来执行外部命令。这里命令指脚本。
# 添加shell脚本,进入容器后自动执行该脚本,使想要的程序在容器中运行起来。该脚本一般放在该项目目录下,方便管理.
# 注意,这个/xxx/start.sh是指的是容器下面的路径,这个路径必须在容器中存在,否则会报start.sh找不到的错误。
entrypoint:
- /xxx/start.sh
这是我的程序的start.sh内容,我是在容器使用supervisord服务来管理程序的,方便管理程序。
supvisord安装:ubuntu直接 apt-get install supervisord 即可。
如果是离线安装Supervisor,可以参考我这篇文章,内容都是类似的:Ubuntu下,python3下离线安装Supervisor。
大家需要根据自己的程序来编写:
#!/bin/bash
# file or directory handle...
if [ ! -f "/etc/supervisor/conf.d/ptz-rtsp_url-hk.conf" ]; then
touch /etc/supervisor/conf.d/ptz-rtsp_url-hk.conf
echo "create file: /etc/supervisor/conf.d/ptz-rtsp_url-hk.conf"
else
echo "the file: /etc/supervisor/conf.d/ptz-rtsp_url-hk.conf has exist"
fi
if [ ! -d "/var/ptz-rtsp_url-hk_log/" ]; then
mkdir -p "/var/ptz-rtsp_url-hk_log/"
echo "create dir: /var/ptz-rtsp_url-hk_log/"
else
echo "the dir: /var/ptz-rtsp_url-hk_log/ has exist"
fi
if [ ! -f "/var/ptz-rtsp_url-hk_log/ptz-rtsp_url-hk.log" ]; then
touch "/var/ptz-rtsp_url-hk_log/ptz-rtsp_url-hk.log"
echo "create file: /var/ptz-rtsp_url-hk_log/ptz-rtsp_url-hk.log"
else
echo "the file: /var/ptz-rtsp_url-hk_log/ptz-rtsp_url-hk.log has exist"
fi
if [ ! -f "/var/ptz-rtsp_url-hk_log/ptz-rtsp_url-hk_error.log" ]; then
touch "/var/ptz-rtsp_url-hk_log/ptz-rtsp_url-hk_error.log"
echo "create file: /var/ptz-rtsp_url-hk_log/ptz-rtsp_url-hk_error.log"
else
echo "the file: /var/ptz-rtsp_url-hk_log/ptz-rtsp_url-hk_error.log has exist"
fi
echo "-----------------file or directory check handle success-----------------"
echo "# 1
[program:ptz-rtsp_url-hk]
# 2 程序在该路径下运行
directory=容器卷路径/PTZ-RTSP_URL-HK/bin/
#运行用户root
user = root
# 3 运行
command=容器卷路径/PTZ-RTSP_URL-HK/bin/ptz-rtsp_url-hk
#在supervisord 启动的时候也自动启动
autostart = true
#启动 1 秒后没有异常退出,就当作已经正常启动了
startsecs = 0
# 停止等待时间
stopwaitsecs=10
#程序异常退出后自动重启
autorestart = true
#启动失败自动重试次数,默认是 3
startretries = 100
# 4 日志文件若文件夹不存在需新建
stdout_logfile=/var/ptz-rtsp_url-hk_log/ptz-rtsp_url-hk.log
# 5 错误日志
stderr_logfile=/var/ptz-rtsp_url-hk_log/ptz-rtsp_url-hk_error.log
#设置环境变量
environment=LOG_DIR="/var/ptz-rtsp_url-hk_log"
" >> /etc/supervisor/conf.d/ptz-rtsp_url-hk.conf
echo "-----------------configure file handle success-----------------"
# start supervisord service and dynamically viewing logs
/usr/bin/supervisord -c /etc/supervisor/supervisord.conf
supervisorctl tail -f ptz-rtsp_url-hk
echo "-----------------start.sh handle success-----------------"
添加stop.sh:
#!/bin/bash
supervisorctl stop ptz-rtsp_url-hk
killall supervisord
然后添加权限。
chmod 755 start.sh
chmod 755 stop.sh
9.2 重新编译程序及配置动态库路径
然后把自己的整个程序目录拷贝到容器卷所在目录。
# 1. 先把未加上脚本命令的容器删除,然后更新该容器。
cd 容器卷下的项目目录。
sudo docker-compose rm # 需要去到yml目录下执行
# 2. 创建容器。
sudo docker-compose up -d
sudo docker-compose ps
容器成功启动。
虽然启动成功,但是由于程序未重新编译,所以进入容器会看到程序开启失败。
所以需要进入容器后,重新编译程序,然后vim /etc/ld.so.conf配置动态库路径, 然后source /etc/ld.so.conf更新配置。
如果不重新编译,即使配置了动态库路径,程序可能仍会链接不到对应的动态库。
# 1. 进入容器
sudo docker exec -it docker_ubuntu_gw /bin/bash
# 2,. 在容器中进入到项目所在目录,然后使用ldd查看,可以看到动态库是没有链接进来的。
cd 项目所在目录
ldd bin/ptz-rtsp_url-hk | grep not
解决:
# 1. 先停止服务。
supervisorctl stop ptz-rtsp_url-hk
# 2. 添加你使用到的动态库目录
vim /etc/ld.so.conf
# 3. 更新配置
ldconfig /etc/ld.so.conf
ldd bin/ptz-rtsp_url-hk | grep not # 此时再看,已经没有not found
# 4. 开启服务。
supervisorctl start ptz-rtsp_url-hk
supervisorctl status ptz-rtsp_url-hk
supervisorctl tail -f ptz-rtsp_url-hk
查看日志,程序开启成功。
9.3 测试开机启动
通过上面后,我们即可将程序伴随着容器的开机自启而启动。所以此时我们可以进行重启测试,重启之前最好把容器先stop掉,这样更方便看到是否开机自启,不过关不关问题都不大。
下图看到,重启后,容器是自启的。然后进入容器使用top查看后,容器里的程序也是开启成功的。
既然程序开机自启成功,那么我们可以尝试一下自己程序的接口是否能访问成功。
下图是使用 supervisorctl tail -f 程序名 查看日志,然后请求我程序的http接口,同样是正常返回的,说明大功告成。