> 在后端语言是php的项目或者公司中,如果希望使用docker开发环境,那么如果直接使用传统docker的build镜像或者dockerfile那一套流程,对于开发者不太友好,而且过程会较复杂 > 因此,开发者们希望有一个类似vagrant的平台,他们没有太多特殊的配置,只是希望有一个和线上相同版本和配置的php的开发环境,特别是新人入职时,可以很快就生成一套开发环境 > 虽然Docker总在强调的一句话就是:一个容器只运行单个进程。本质上容器运行多个进程不会有什么问题,如果涉及到容器编排,那多进程容器在容器健康状态方面可能会比较麻烦,但是在开发环境中,一个容器多个进程是不会有什么问题的 ### 理论部分 1. 核心想法是将docker作为虚拟机使用,使用一台宿主机,在其安装Docker, 为每人启动一个容器,容器内部运行了完整的开发需要的进程,比如多个php版本,redis,nginx等应用 2. 公司内使用的DNS是自建DNS服务 3. 开发者电脑开启samba共享,宿主机将此目录挂载到本地磁盘上,这个本地磁盘目录又被挂载到容器内部 ### 架构图  ### 为新开发者创建开发环境 只需要让开发者配置好samba共享,然后使用创建脚本即可生成自己的开发环境 ### 请求流程 - 在开发者自己的电脑上,按规则新建对应的项目目录,必须以\*.dev.项目名为文件名(\*为自己的用户名) - 此目录被宿主机挂载,又被宿主机挂载进容器内部 - 访问该域名(上一步新建的目录名)(*.dev.项目名开头的域名会被收录进DNS,这步需要将域名加入DNS) - 这些统配域名统一被解析到Docker宿主机,请求到宿主机上的openresty - 由lua脚本检测到该域名前缀的用户名,然后使用脚本获取用户对应的容器IP,将请求反向代理到这个容器 - 容器内部Nginx收到请求,使用域名通配,root目录通配,root定为到对应的目录,然后proxy_pass到php-fpm处理 - mysql暂时没有容器化,统一访问的是开发环境的mysql ### openresty中nginx的配置文件 openresty安装目录为 `/usr/local/openresty/` ``` ~]# cat /usr/local/openresty/nginx/conf/nginx.conf worker_processes 1; events { worker_connections 1024; } http { client_max_body_size 100m; include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { set_by_lua $info ' local s = ngx.var.host local t = io.popen("echo " ..s.. " |grep -Eo ^[^.]+") name = t:read("*l") t:close() local file = io.popen("grep " ..name.. " /usr/local/openresty/nginx/server_name_ip") content = file:read("*l") file:close() return content '; if ($info ~ (.*)\s(.*)) { set $proxy_ip $2; } listen 80; server_name default; location / { proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://$proxy_ip:80; } } include conf.d/*.conf; } ``` /usr/local/openresty/nginx/server_name_ip是为用户创建一个开发环境时就会写入一条配置,记录用户名和容器IP,内容例: ``` zhangsan 172.17.0.2 lisi 172.17.0.3 wangwu 172.17.0.4 ``` ### 容器内nginx配置 ``` worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; log_format main '"$http_x_forwarded_for" - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" $remote_addr'; sendfile on; keepalive_timeout 65; server { listen 80 default_server; root /data/dev.www/${host}; access_log /data/dev.www/logs/access.log main; error_log /data/dev.www/logs/error.log; index index.html index.php; location / { try_files $uri $uri/ /index.php$is_args$args; } location ~ \.php$ { try_files $uri =404; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; if ($host !~ (.*news\.7654\.com$|.*guangsussapi\.com$)) { fastcgi_pass 127.0.0.1:9055; } if ($host ~ .*news\.bbb\.com$) { fastcgi_pass 127.0.0.1:9072; } if ($host ~ .*xxx\.com$) { fastcgi_pass 127.0.0.1:9072; } } } } ``` ### 创建开发环境的脚本 用法:Script.sh -u USERNAME -h IP_ADDR -p PASSWORD -s windows|mac **create_dev.sh** ``` #!/bin/bash source /etc/init.d/functions images_version='zm-dev-base:v2.7' while getopts "u:h:p:s:" opt;do case $opt in u) name="${OPTARG}" ;; h) Client_IP="${OPTARG}" ;; p) PASSWORD="${OPTARG}" ;; s) SYSTEM="${OPTARG}" ;; *) echo "Usage: Script.sh -u USERNAME -h IP_ADDR -p PASSWORD -s windows|mac" exit 3 ;; esac done check(){ #格式化输出结果 if [ $? -ne 0 ];then action "$1" /bin/false exit else action "$1" /bin/true fi } useradd_user(){ # 创建用户,对用户web目录授权 output=`/bin/bash /app/shell/useradd_user.sh ${name}` if [ -z "${output}" ];then mkdir -pv /data/dev.www/${name} &> /dev/null chown -R ${name}:${name} /data/dev.www/${name} USER_STATUS='True' else mkdir -pv /data/dev.www/${name} &> /dev/null chown -R ${name}:${name} /data/dev.www/${name} echo > /dev/null check "Adduser: ${name} already exists" fi } mount_test() { umount /mnt &> /dev/null #判断操作系统,选择对应的samba版本号 if [[ "${SYSTEM}" == "windows" ]];then SYS_VERSION=1.0 elif [[ "${SYSTEM}" == "mac" ]];then SYS_VERSION=3.0 fi mount -t cifs -o uid=${name},gid=${name},username=${name},password=${PASSWORD},dir_mode=0777,file_mode=0777,vers=${SYS_VERSION} //${Client_IP}/dev.www /mnt check "Mount Test" if grep -q "${name}" /app/shell/mount_info;then sed -i "s/${name}.*/${name} ${Client_IP} ${PASSWORD} ${SYS_VERSION}/" /app/shell/mount_info else echo "${name} ${Client_IP} ${PASSWORD} ${SYS_VERSION}" >> /app/shell/mount_info fi umount /mnt } start_docker(){ #挂载用户samba目录 mount -t cifs -o uid=${name},gid=${name},username=${name},password=${PASSWORD},dir_mode=0777,file_mode=0777,vers=${SYS_VERSION} //${Client_IP}/dev.www /data/dev.www/${name} #创建目录 mkdir -p /data/dev.www/${name}/logs #检测是否已启动容器 if docker ps | grep -q "${name}";then echo "Info: ${name} already start" exit fi # 启动容器 docker rm ${name} &> /dev/null docker run -it -d --hostname="web_docker" -c=512 -m 2g --device-read-bps /dev/mapper/centos-root:50mb --name ${name} -v /etc/localtime:/etc/localtime:ro -v /data/dev.www/${name}:/data/dev.www ${images_version} /usr/sbin/init &> /dev/null #cp nginx.conf docker cp /ngx_conf/nginx.conf ${name}:/app/nginx/conf/nginx.conf # 启动服务 docker exec -d ${name} /bin/bash /app/shell/start_dev.sh #chown -R ${name}:${name} /data/dev.www/${name} #获取容器IP IP=`docker exec ${name} ip addr | grep "inet.*eth0" |awk '{print $2}'| awk -F/ '{print $1}'` # 关联容器IP if grep -q "${name}" /usr/local/openresty/nginx/server_name_ip;then sed -i "/${name}/s/.*/${name} ${IP}/" /usr/local/openresty/nginx/server_name_ip else echo "${name} ${IP}" >> /usr/local/openresty/nginx/server_name_ip fi if docker ps | grep -q "${name}";then check "start docker ${name}" echo else check "start docker ${name}" fi } send_mail(){ echo "${PASSWORD}" | passwd --stdin ${name} &> /dev/null if ! grep -q ${name} /etc/sudoers;then echo "${name} ALL=NOPASSWD: /usr/bin/docker" >> /etc/sudoers fi if ! grep -q "sudo docker" /home/${name}/.bashrc;then echo "sudo docker exec -it ${name} /bin/bash ; exit" >> /home/${name}/.bashrc fi sed "s/dev_name/${name}/" /app/shell/mail_info | sed "s/password/${PASSWORD}/" | mailx -s "dev环境账户创建" ${name}@shzhanmeng.com } if [ -z "${name}" -o -z "${Client_IP}" -o -z "${PASSWORD}" ];then echo "Usage: Script.sh -u USERNAME -h IP_ADDR -p PASSWORD -s windows|mac" exit 1 fi if [ "${SYSTEM}" != "windows" -a "${SYSTEM}" != "mac" ];then echo "Usage: Script.sh -u USERNAME -h IP_ADDR -p PASSWORD -s windows|mac" exit 1 fi umount /data/dev.www/${name} &> /dev/null useradd_user mount_test start_docker if [ "${USER_STATUS}" != 'True' ];then send_mail fi ``` 注意: mac版本的samba一般是samba3.0,默认win10使用的是1.0版本, 所以在这里使用-s指定是用的哪个系统的选项 ### 添加用户脚本useradd_user.sh,仅被创建开发环境的脚本调用 ``` #!/bin/bash if [ -z "$1" ];then echo Input Err fi name=$1 mail(){ useradd $1 } id ${name} &> /dev/null if [ $? -eq 0 ];then echo "Info: ${name} already exists" else mail ${name} fi ``` ### 检测用户主机是否在线脚本 若不检测用户主机是否在线,并及时卸载,则当用户主机关机时,在宿主机上不能使用df等命令 此脚本应该后台长期运行,及时剔除下线主机,并卸载该主机对应的samba目录 当该主机上线时,自动给其挂载samba目录 samba挂载需要的信息被存于 mount_info这个文件中,该文件由`创建开发环境的脚本`生成 mount_info ``` zhangsan 172.18.15.155 asda@qq 1.0 lisi 172.18.23.24 115aasyu 1.0 wangwu 172.18.22.82 estineuan 1.0 ``` onlineCheck.sh ``` #!/bin/bash output='/app/shell/check_line.txt' while read line;do set -- ${line} Name=$1 Ip=$2 Password=$3 Version=$4 ping -c2 -w4 $Ip &> /dev/null #判断主机是否在线;不在线-->判断是否在挂载中,如果是则卸载,无论卸载命令是否成功均追加至$(output)中 #如果主机在线,但未挂载,则将相关用户挂载,无论挂载结果如何,将挂载信息追加至$(output) if [ $? -ne 0 ];then if mount|grep -q "/data/dev.www/${Name}";then umount -f /data/dev.www/${Name} &> /dev/null # -f 强制卸载,不然客户端离线时会卡住 if [ "$?" -eq 0 ];then echo -e "`date +%F-%H:%M:%S`: umount /data/dev.www/${Name} \033[32mSUCCESS\033[0m!" >> ${output} else echo -e "`date +%F-%H:%M:%S`: umount /data/dev.www/${Name} \033[31mFAILED\033[0m!" >> ${output} fi fi else if ! mount|grep -q "/data/dev.www/${Name}";then mount -t cifs -o uid=${Name},gid=${Name},username=${Name},password=${Password},dir_mode=0777,file_mode=0777,vers=${Version} //${Ip}/dev.www /data/dev.www/${Name} &> /dev/null if [ "$?" -eq 0 ];then echo -e "`date +%F-%H:%M:%S`: mount /data/dev.www/${Name} \033[32mSUCCESS\033[0m!" >> ${output} else echo -e "`date +%F-%H:%M:%S`: mount /data/dev.www/${Name} \033[31mFAILED\033[0m!" >> ${output} fi fi fi done < /app/shell/mount_info ``` ### DNS配置 具体的域名解析配置文件 ``` $TTL 1D $TTL 600 @ IN SOA ns.dev.project1.xxx.com. root.dev.project1.xxx.com. ( 0 ; serial 1D ; refresh 1H ; retry 1W ; expire 3H ) ; minimum IN NS ns IN A 172.18.15.15 ns IN A 172.18.15.15 * IN A 172.18.15.15 ``` 这里的域使用二级域,一般情况下这里都是xxx.com一级域,但是为了匹配我们的域名规则 `姓名.dev.项目.根域`,所以有这个DNS配置的设计 named-zones配置 ``` zone "dev.project1.xxx.com" IN { #这个是正向 type master; file "/var/named/dev-docker/dev.project1.xxx.com.zone"; allow-update { none; }; }; zone "com.xxx.project1.dev.in-addr.arpa" IN { #这个是反向 type master; file "/var/named/dev-docker/dev.project1.xxx.com.zone"; allow-update { none; }; }; ``` ### 将开发环境域名加入公司DNS 所有的开发环境域名 dns_domain ``` abcapi.xiaoluduoduo.com abckantu.7654.com ``` make_dns.sh ``` #!/bin/bash echo > /etc/named.dev-docker.zones rm -rf /var/named/dev-docker/* &> /dev/null while read line;do rev=`echo "${line}" | awk -F. '{for(i=NF;i>=1;i--)printf $i"."}'` cat << EOF >> /etc/named.dev-docker.zones zone "dev.${line}" IN { #这个是正向 type master; file "/var/named/dev-docker/dev.${line}.zone"; allow-update { none; }; }; zone "${rev}dev.in-addr.arpa" IN { #这个是反向 type master; file "/var/named/dev-docker/dev.${line}.zone"; allow-update { none; }; }; EOF cat << EOF >> /var/named/dev-docker/dev.${line}.zone \$TTL 1D \$TTL 600 @ IN SOA ns.dev.${line}. root.dev.${line}. ( 0 ; serial 1D ; refresh 1H ; retry 1W ; expire 3H ) ; minimum IN NS ns IN A 172.18.15.15 ns IN A 172.18.15.15 * IN A 172.18.15.15 EOF done < /app/shell/dns_domain service named reload ``` ### 客户机samba配置  ### 使用帮助手册   ### 启动流程 添加新同学 - 运行脚本 /app/shell/create_dev.sh -u USERNAME -p IPADDR -P PASSWORD -s windows|mac 会调用以下3个函数 - useradd_user添加用户 创建用户 ${name} ,创建/data/dev.www/${name}, 修改权限 chown -R ${name}:${name} /data/dev.www/${name} mount_test 挂载测试 - mount -t cifs -o uid=${name},gid=${name},username=${name},password=${PASSWORD},dir_mode=0777,file_mode=0777,vers=${SYS_VERSION} //${Client_IP}/dev.www /mnt - 测试挂载成功后将用户名、IP、密码、系统类型写入/app/shell/mount_info文件中 start_docker - 挂载用户samba目录 - 创建log目录 /data/dev.www/${name}/logs - 检测该用户容器是否已启动 - 删除之前未删除的僵尸容器 - 以systemd启动容器,限制CPU,内存,磁盘使用率 - 挂载/data/dev.www/${name}:/data/dev.www目录 - 复制nginx.conf配置文件 - 启动容器内各服务组件 nginx php5 php7 php7.2 redis - 获取容器IP,将用户名和IP记录到/usr/local/openresty/nginx/server_name_ip mail - 在docker宿主机为该用户设置密码,密码为samba挂载的密码 - 添加visudo 可使用 sudo docker - 将sudo docker exec -it ${name} /bin/bash ; exit 写入该用户 ~/.bashrc以限制该用户登录宿主机时直接登录到自己的容器内 - 给用户发送邮件,登录名、主机、密码、端口等信息 服务 - 宿主机 - 宿主机启动组件 - mysql-5.6 - 172.17.0.1:3306 nginx代理 - 由域名前缀反代到对应容器,使用ngx_lua 目录挂载映射 - 宿主机 ==> 容器内 - /data/dev.www/${name} - /data/dev.www - 容器启动组件 - nginx1.9.7 ==> 0.0.0.0:80 - php5.5 ==> 127.0.0.1:9055 - php7.0 ==> 127.0.0.1:9070 - redis3.2 ==> 127.0.0.1:6379 - php7.2 ==> 127.0.0.1:9072 ### 扩展 如何登录进自己的容器 在创建开发环境脚本中,有如下命令 ``` if ! grep -q "sudo docker" /home/${name}/.bashrc;then echo "sudo docker exec -it ${name} /bin/bash ; exit" >> /home/${name}/.bashrc fi ``` - 用户使用 用户名@宿主机IP 登录宿主机,在执行用户.bashrc时会执行sudo docker exec -it ${name} /bin/bash ; exit - 登录时执行docker exec就登录进了自己的容器 - 退出容器后,接着运行exit命令,直接退出宿主机 ### 结语 至此,自动化开发环境搭建完成 Last modification:August 7th, 2019 at 04:24 pm © 允许规范转载 Support 如果觉得我的文章对你有用 ×Close Appreciate the author Sweeping payments
友链来一波www.17mark.com
非技术的路过。