容器基础(六): 应用程序容器化

概述

传统的一些服务器程序,通常是通过读配置文件的方式来读入参数,  如果要把程序容器化,通过配置文件读参就存在不方便的情况。现在以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
/app/original/[server|worker]/config.py

  

 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 + "
")
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 '
[-] 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 '
Exit'
/app/original/server/server.py
 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"]
/app/original/server/Dockerfile
1 [listen]
2 port = 3000
3 
4 [log]
5 level = LOG_INFO
/app/original/server/ini/config.ini
 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 + "
")
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 '
[-] 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 '
Exit'
/app/original/worker/worker.py
 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"]
/app/original/worker/Dockerfile
1 [server]
2 ip = 127.0.0.1
3 port = 3000
4 
5 [log]
6 level = LOG_INFO
/app/original/worker/ini/config.ini

应用程序容器化方法

一. 使用共享网络(配置--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 + "
")
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 '
[-] 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 '
Exit'
/app/update/server/server.py
 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
/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 + "
")
 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 '
[-] 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 '
Exit'
/app/update/worker/worker.py
 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"]
/app/update/worker/Dockerfile

  

通过传参调用结果如下:

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 #

三. 参数通过环境变量传入

具体参考容器基础(七): 使用docker compose部署程序

Excellence, is not an act, but a habit.
作者:子厚.
出处:http://www.cnblogs.com/aios/
本文版权归作者和博客园共有,欢迎转载、交流、点赞、评论,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

原文地址:https://www.cnblogs.com/aios/p/9980726.html