概述
传统的一些服务器程序,通常是通过读配置文件的方式来读入参数, 如果要把程序容器化,通过配置文件读参就存在不方便的情况。现在以debian-python27为基础镜像, 以一个脚本程序为例来进行程序容器化改造! 改造前后的目录结构如下所示:
linux:/app # tree . ├── original # 初始代码目录, 模拟老的服务器程序, 日志文件位于log/server.log │ ├── server # 服务端, 通过Dockerfile打包的镜像名称: original/server:v0.1 │ │ ├── Dockerfile # server程序打包Dockerfile │ │ ├── config.py # 读取配置文件的辅助模块 │ │ ├── ini │ │ │ └── config.ini # 配置文件 │ │ └── server.py # 监听并回送客户端发送的字符串! 需从ini/config.ini读取监听端口 │ └── worker # 客户端, 日志文件位于log/worker.log, 对应镜像: original/worker:v0.1 │ ├── Dockerfile │ ├── config.py # 读取配置文件的辅助模块, 和server/config.py相同 │ ├── ini │ │ └── config.ini # 配置文件, 包含连接ip,port等 │ └── worker.py # 客户端程序, 读取ini/config.ini连接到server, 发送"hello, docker!"给server └── update # 经过改造的程序目录, 删除了读取配置文件, 其它文件和original目录下一致 ├── server # 改造后对应镜像: update/server:v0.1 │ ├── Dockerfile │ └── server.py └── worker # 改造后对应镜像: update/worker:v0.1 ├── Dockerfile └── worker.py 8 directories, 12 files linux:/app #
初始程序代码如下:
1 #!/usr/bin/python 2 # -*- coding: utf-8 -*- 3 4 import os 5 import sys 6 import ConfigParser 7 8 class Config: 9 def __init__(self, ini_path): 10 self.config_ = ConfigParser.ConfigParser() 11 self.config_.read(ini_path) 12 13 def get_string(self, section, key, default_string_value): 14 try: 15 return self.config_.get(section, key) 16 except Exception, e: 17 return default_string_value 18 19 def get_int(self, section, key, default_int_value): 20 try: 21 return int(self.config_.get(section, key)) 22 except Exception, e: 23 return default_int_value
1 #!/usr/bin/python 2 # -*- coding: utf-8 -*- 3 4 import os 5 import sys 6 import time 7 import socket 8 import select 9 import signal 10 import threading 11 from config import Config 12 13 log_file = "log/server.log" 14 15 def log(msg): 16 if not os.path.exists("log"): 17 os.mkdir("log") 18 with open(log_file, "a") as wf: 19 wf.writelines( 20 time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) + 21 " [INFO] " + msg + "\n") 22 23 def do_listen(): 24 cfg = Config("ini/config.ini") 25 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 26 server_socket.bind(('', cfg.get_int("listen", "port", 3000))) 27 server_socket.listen(5) 28 print 'Waiting for connection...' 29 log('Waiting for connection...') 30 server_socket.setblocking(0) 31 32 rfd = [server_socket, ] 33 34 while True: 35 rlist, wlist, xlist = select.select(rfd, [], []) 36 for sock in rlist: 37 if sock == server_socket: 38 newsock, addr = server_socket.accept() 39 print '[+] %s connected' % str(addr) 40 log('%s connected' % str(addr)) 41 rfd.append(newsock) 42 else: 43 data = sock.recv(1024) 44 if data: 45 sock.send(data) 46 else: 47 print '[-] %s closed' % str(sock.getpeername()) 48 log('%s closed' % str(sock.getpeername())) 49 rfd.remove(sock) 50 sock.close() 51 52 def signal_handler(signum, frame): 53 print '\n[-] signal(%d) received, exit!' % signum 54 log('signal(%d) received, exit!' % signum) 55 sys.exit(-1) 56 57 if __name__ == '__main__': 58 signal.signal(signal.SIGINT, signal_handler) 59 60 try: 61 do_listen() 62 except Exception, e: 63 print e 64 print '\nExit'
1 FROM jason/debian-python27:v1.0 2 3 MAINTAINER jason<djsxut@163.com> 4 5 RUN mkdir -p /original/server 6 7 COPY . /original/server 8 9 WORKDIR /original/server 10 11 ENV PATH $PATH:/original/server 12 13 ENTRYPOINT ["python", "server.py"]
1 [listen] 2 port = 3000 3 4 [log] 5 level = LOG_INFO
1 #!/usr/bin/python 2 # -*- coding: utf-8 -*- 3 4 import os 5 import sys 6 import time 7 import socket 8 import signal 9 import threading 10 from config import Config 11 12 log_file = "log/worker.log" 13 14 def log(msg): 15 if not os.path.exists("log"): 16 os.mkdir("log") 17 with open(log_file, "a") as wf: 18 wf.writelines( 19 time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) + 20 " [INFO] " + msg + "\n") 21 22 def do_handler(): 23 cfg = Config("ini/config.ini") 24 sc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 25 while True: 26 try: 27 sc.connect((cfg.get_string("server", "ip", "127.0.0.1"), \ 28 cfg.get_int("server", "port", 3000))) 29 except Exception, e: 30 print '[-] connect failed...' 31 log('connect failed...') 32 time.sleep(5) 33 else: 34 break 35 36 while True: 37 # do something 38 sc.send('hello, docker!') 39 data = sc.recv(1024) 40 if data: 41 print '[+][recv] %s' % data 42 log('[recv] %s' % data) 43 time.sleep(5) 44 else: 45 print '[-] server closed, exit' 46 log('server closed, exit') 47 sc.close() 48 break 49 50 def signal_handler(signum, frame): 51 print '\n[-] signal(%d) received, exit!' % signum 52 log('signal(%d) received, exit!' % signum) 53 sys.exit(-1) 54 55 if __name__ == '__main__': 56 signal.signal(signal.SIGINT, signal_handler) 57 try: 58 do_handler() 59 except Exception, e: 60 print e 61 print '\nExit'
1 FROM jason/debian-python27:v1.0 2 3 MAINTAINER jason<djsxut@163.com> 4 5 RUN mkdir -p /original/worker 6 7 COPY . /original/worker 8 9 WORKDIR /original/worker 10 11 ENV PATH $PATH:/original/worker 12 13 ENTRYPOINT ["python", "worker.py"]
1 [server] 2 ip = 127.0.0.1 3 port = 3000 4 5 [log] 6 level = LOG_INFO
应用程序容器化方法
一. 使用共享网络(配置--net=host), 配置文件通过数据卷挂载到容器
优点是程序不用修改,缺点是隔离性减少。
通过Dockerfile构建镜像,执行结果如下:
linux:/app/original/server # docker run -d --rm --net=host -v /app/original/server/ini/:/original/server/ini:ro original/server:v0.1 4211d8846cf21f62baf9beddb65dd795c47ed1495d3a47d7cb139c3d45db7963 linux:/app/original/server # netstat -apn | grep 3000 tcp 0 0 0.0.0.0:3000 0.0.0.0:* LISTEN 11353/python linux:/app/original/server # docker exec 4211d884 tail -f /original/server/log/server.log 2018-11-19 08:26:17 [INFO] Waiting for connection... 2018-11-19 08:26:57 [INFO] ('127.0.0.1', 43628) connected ^C linux:/app/original/server # netstat -apn | grep 3000 tcp 0 0 0.0.0.0:3000 0.0.0.0:* LISTEN 11353/python tcp 0 0 127.0.0.1:43628 127.0.0.1:3000 ESTABLISHED 11395/python tcp 0 0 127.0.0.1:3000 127.0.0.1:43628 ESTABLISHED 11353/python linux:/app/original/server # linux:/app/original/worker # docker run -d --rm --net=host -v /app/original/worker/ini/:/original/worker/ini:ro original/worker:v0.1 f7dbbcfb39b12f4c080c6854777d0f5cf3b173a79c4be5104e9d0f8a4e61471a linux:/app/original/worker # docker exec f7dbbcfb tail -f /original/worker/log/worker.log tail: unrecognized file system type 0x794c7630 for '/original/worker/log/worker.log'. please report this to bug-coreutils@gnu.org. reverting to polling 2018-11-19 08:27:07 [INFO] [recv] hello, docker! ^C linux:/app/original/worker #
二. 参数通过传参传入(其它参数或者固化或者依然通过挂载方式传入容器)
优点是隔离性好, 配置灵活, 对于示例程序,先通过传参传入域名, 然后通过DNS解析获取IP地址,对多机器部署能带来方便;缺点是程序需要改动。
修改代码如下:
1 #!/usr/bin/python 2 # -*- coding: utf-8 -*- 3 4 import os 5 import sys 6 import time 7 import socket 8 import select 9 import signal 10 import threading 11 12 log_file = "log/server.log" 13 14 def log(msg): 15 if not os.path.exists("log"): 16 os.mkdir("log") 17 with open(log_file, "a") as wf: 18 wf.writelines( 19 time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) + 20 " [INFO] " + msg + "\n") 21 22 def do_listen(port): 23 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 24 server_socket.bind(('', port)) 25 server_socket.listen(5) 26 print 'Waiting for connection...' 27 log('Waiting for connection...') 28 server_socket.setblocking(0) 29 30 rfd = [server_socket, ] 31 32 while True: 33 rlist, wlist, xlist = select.select(rfd, [], []) 34 for sock in rlist: 35 if sock == server_socket: 36 newsock, addr = server_socket.accept() 37 print '[+] %s connected' % str(addr) 38 log('%s connected' % str(addr)) 39 rfd.append(newsock) 40 else: 41 data = sock.recv(1024) 42 if data: 43 sock.send(data) 44 else: 45 print '[-] %s closed' % str(sock.getpeername()) 46 log('%s closed' % str(sock.getpeername())) 47 rfd.remove(sock) 48 sock.close() 49 50 def signal_handler(signum, frame): 51 print '\n[-] signal(%d) received, exit!' % signum 52 log('signal(%d) received, exit!' % signum) 53 sys.exit(-1) 54 55 if __name__ == '__main__': 56 try: 57 port = int(sys.argv[2]) 58 except: 59 print 'usage: server.py [-p listen_port]' 60 log('usage: server.py [-p listen_port]') 61 sys.exit(-1) 62 63 signal.signal(signal.SIGINT, signal_handler) 64 65 try: 66 do_listen(port) 67 except Exception, e: 68 print e 69 print '\nExit'
1 FROM jason/debian-python27:v1.0 2 3 MAINTAINER jason<djsxut@163.com> 4 5 RUN mkdir -p /update/server 6 7 COPY . /update/server 8 9 WORKDIR /update/server 10 11 ENV PATH $PATH:/update/server 12 13 #EXPOSE 3000 14 15 ENTRYPOINT ["python", "server.py"] 16 17 /app/update/server/Dockerfile
1 #!/usr/bin/python 2 # -*- coding: utf-8 -*- 3 4 import os 5 import sys 6 import time 7 import socket 8 import signal 9 import getopt 10 import threading 11 12 log_file = "log/worker.log" 13 14 def log(msg): 15 if not os.path.exists("log"): 16 os.mkdir("log") 17 with open(log_file, "a") as wf: 18 wf.writelines( 19 time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) + 20 " [INFO] " + msg + "\n") 21 22 class Handler: 23 def __init__(self, domain, port): 24 self.domain = domain 25 self.port = port 26 self.sock = None 27 28 def get_socket(self): 29 for res in socket.getaddrinfo(self.domain, self.port, 30 socket.AF_INET, socket.SOCK_STREAM): 31 family, socktype, proto, canonname, sa = res 32 try: 33 self.sock = socket.socket(family, socktype, proto) 34 #self.sock.setblocking(0) 35 #sock.settimeout(0.5) 36 self.sock.connect(sa) 37 except socket.error, msg: 38 if self.sock != None: 39 self.sock.close() 40 self.sock = None 41 else: 42 return True 43 44 return False 45 46 def do_handler(self): 47 while self.sock == None and self.get_socket() == False: 48 print '[-] connect failed...' 49 log('connect failed...') 50 time.sleep(5) 51 52 print '[+] %s connected' % str(self.sock.getpeername()) 53 log('[+] %s connected' % str(self.sock.getpeername())) 54 while True: 55 # do something 56 self.sock.send('hello, docker!') 57 data = self.sock.recv(1024) 58 if data: 59 print '[+][recv] %s' % data 60 log('[recv] %s' % data) 61 time.sleep(5) 62 else: 63 print '[-] server closed, exit' 64 log('server closed, exit') 65 self.sock.close() 66 self.sock = None 67 break 68 69 def signal_handler(signum, frame): 70 print '\n[-] signal(%d) received, exit!' % signum 71 log('signal(%d) received, exit!' % signum) 72 sys.exit(-1) 73 74 def usage(): 75 print 'Usage: worker.py option' + \ 76 ' [-d domain]' + \ 77 ' [-p port]' + \ 78 ' [-h]' 79 80 def get_params(argvs): 81 domain = None 82 port = None 83 try: 84 opts, args = getopt.getopt(argvs[1:], "hd:p:", [""]) 85 for op, value in opts: 86 if op == "-d": 87 domain = value 88 elif op == "-p": 89 port = int(value) 90 except Exception, e: 91 usage() 92 sys.exit(-1) 93 94 if domain == None or port == None: 95 usage() 96 sys.exit() 97 98 return domain, port 99 100 if __name__ == '__main__': 101 signal.signal(signal.SIGINT, signal_handler) 102 domain, port = get_params(sys.argv) 103 104 try: 105 handler = Handler(domain, port) 106 handler.do_handler() 107 except Exception, e: 108 print e 109 print '\nExit'
1 FROM jason/debian-python27:v1.0 2 3 MAINTAINER jason<djsxut@163.com> 4 5 RUN mkdir -p /update/worker 6 7 COPY . /update/worker 8 9 WORKDIR /update/worker 10 11 ENV PATH $PATH:/update/worker 12 13 ENTRYPOINT ["python", "worker.py"]
通过传参调用结果如下:
linux:/app/update/server # docker run -d --rm --name server update/server:v0.1 -p 3000 5868163fa574810269ca03f60b30aa473c9bed0a0f55e73678b648fd56f8a722 linux:/app/update/server # docker exec 5868 tail -f /update/server/log/server.log tail: unrecognized file system type 0x794c7630 for '/update/server/log/server.log'. please report this to bug-coreutils@gnu.org. reverting to polling 2018-11-19 08:11:08 [INFO] Waiting for connection... 2018-11-19 08:16:52 [INFO] ('172.17.0.3', 59538) connected linux:/app/update/server # linux:/app/update/worker # docker run -d --rm --name worker --link server:server update/worker:v0.1 -d server -p 3000 851ba3bc9b74ad022f2df9e4d9639c696485e4687e47197111f442e66de19c7d linux:/app/update/worker # docker exec 851ba tail -f /update/worker/log/worker.log 2018-11-19 08:16:52 [INFO] [+] ('172.17.0.2', 3000) connected 2018-11-19 08:16:52 [INFO] [recv] hello, docker! 2018-11-19 08:16:57 [INFO] [recv] hello, docker! linux:/app/update/worker # docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 851ba3bc9b74 update/worker:v0.1 "python worker.py ..." 2 minutes ago Up 2 minutes worker 5868163fa574 update/server:v0.1 "python server.py ..." 8 minutes ago Up 8 minutes server linux:/app/update/worker # docker exec server ss -a | grep 3000 tcp LISTEN 0 5 *:3000 *:* tcp ESTAB 0 0 172.17.0.2:3000 172.17.0.3:59538 linux:/app/update/worker # docker exec worker ss -a | grep 3000 tcp ESTAB 0 0 172.17.0.3:59538 172.17.0.2:3000 linux:/app/update/worker #