项目部署-Ubuntu
参考文章
- Nginx 安装与部署配置以及Nginx和uWSGI开机自启
- 部署 Django
- 如何在Ubuntu 16.04上使用uWSGI和Nginx服务Django应用程序
- Django 部署(Nginx)
- Nginx面试题
- Python3 基于 Nginx 部署 Django 项目
- Python Web开发最难懂的WSGI协议,到底包含哪些内容?
- 如何使用 WSGI 进行部署
- WSGI的理解(转载)
- WSGI到底是什么?
- 什么是wsgi
- WEB开发——Python WSGI协议详解
- 如何理解wsgi
- Django学习笔记之uWSGI详解
需要更新的地
-
uwsgi中http和socket的不同
-
nginx中server部分
-
docker的基本使用
wsgi
1.1 什么是wsgi
wsgi全称是web server gateway interface
,是一套接口标准协议/规范,指定了web服务器和python web应用或框架之间的标准接口,以提高python框架在服务器之间的移植性。官方文档
我们可以将wsgi
分成两个组件:
- server:常见的又uWSGI,gunicorn
- Application:Django,Flask等中实现的application
1.2 wsgi
有什么用
web应用处理请求的具体流程:
- 用户通过浏览器将请求发送至服务器
- web服务器将请求转交给web应用程序,web应用程序处理请求
- web应用程序将处理结果返回给服务器,服务器返回响应结果给浏览器
- 浏览器收到响应并展示出来
相应的web服务器
和web应用
都有很多,它们如何进行通信呢,就需要指定一种通信标准,WSGI就是一种通信的协议规范。
wsgi作为web服务器和web应用之间的桥梁,主要有两个作用:
- 让Web服务器知道如何调用python应用程序,并把请求发送给python应用程序
- 让python应用程序知道用户的请求是什么,以及如何将处理结果返回Web服务器
1.3 wsgi
的两个方面
wsgi接口具有两个方面:服务器或网关 以及 应用程序或框架端
server端会先收到用户的请求,然后会根据规范的要求调用application端
-
服务器
WSGI服务器端接受HTTP请求,构建environ对象,然后调用application对象,最后将对象返回的HTTP Response返回给浏览器
Web服务器,主要是实现相应的信息转换,将网络请求中的信息,按照HTTP协议将内容拿出,同时按照WSGI协议组装成新的数据,同时将提供的start_response传递给Application。最后接收Application返回的内容,按照WSGI协议解析出。最终按照HTTP协议组织好内容返回就完成了一次请求。
server端需要知道如何去找application的入口,我们在使用server服务器(gunicorn、uwsgi)时都需要指定application的,例如:
gunicorn:配置参数的
wsgi_app
wsgi_app Default: None A WSGI application path in pattern $(MODULE_NAME):$(VARIABLE_NAME).
uwsgi:配置文件的module参数
module = for_test.wsgi:application // 指定wsgi模块下的application对象
下面实践一个简单的server服务器:
""" Created by zhoujianhui on 2021-01-06 """ from cookie_test.asgi import application __author__ = '周剑辉' # coding:utf-8 """ desc: WSGI服务器实现 """ from wsgiref.util import setup_testing_defaults from wsgiref.simple_server import make_server # A relatively simple WSGI application. It's going to print out the # environment dictionary after being updated by setup_testing_defaults def simple_app(environ, start_response): setup_testing_defaults(environ) status = '200 OK' headers = [('Content-type', 'text/plain; charset=utf-8')] start_response(status, headers) ret = [("%s: %s\n" % (key, value)).encode("utf-8") for key, value in environ.items()] return ret with make_server('', 8000, simple_app) as httpd: print("Serving on port 8000...") httpd.serve_forever()
文中简单实现了一个 application和服务器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rPQgWntu-1624859753218)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210330003216559.png)]
-
application/应用程序
应用程序对象只是一个接受两个参数的可调用对象。。术语“对象”不应被误解为需要实际的对象实例:函数,方法,类或具有
__call__
方法的实例都可以用作应用程序对象。当server按照WSGI的规范调用了application之后,application就可以开始处理客户端的请求了,处理请求之后,application对象需要返回处理结果给server端。处理请求和返回结果这两个事情,都和server调用application对象时传递的两个参数有关。
application接收两个参数,两个参数分别是:environ和start_response
- environ是一个字典,里面储存了HTTP request相关的所有内容,比如header、请求参数等等
- start_response是一个WSGI 服务器传递过来的函数,用于将response header,状态码传递给Server。
调用 start_response 函数负责将响应头、状态码传递给服务器, 响应体则由application函数返回给服务器, 一个完整的http response 就由这两个函数提供。
下面实现一个简单的application
HELLO_WORLD = b"Hello world!\n" def application(environ, start_response): status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return [HELLO_WORLD]
我们可以看下django源码中application:
-
1.首先是 wsgi.py
application = get_wsgi_application()
-
get_wsgi_application是一个函数,返回 WSGIHandler实例对象
def get_wsgi_application(): """ The public interface to Django's WSGI support. Return a WSGI callable. Avoids making django.core.handlers.WSGIHandler a public API, in case the internal WSGI implementation changes or moves in the future. """ django.setup(set_prefix=False) return WSGIHandler()
-
WSGIHandler类是一个实现了
__call__
方法的类class WSGIHandler(base.BaseHandler): request_class = WSGIRequest def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.load_middleware() def __call__(self, environ, start_response): set_script_prefix(get_script_name(environ)) signals.request_started.send(sender=self.__class__, environ=environ) request = self.request_class(environ) response = self.get_response(request) response._handler_class = self.__class__ status = '%d %s' % (response.status_code, response.reason_phrase) response_headers = [ *response.items(), *(('Set-Cookie', c.output(header='')) for c in response.cookies.values()), ] start_response(status, response_headers) if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'): # If `wsgi.file_wrapper` is used the WSGI server does not call # .close on the response, but on the file wrapper. Patch it to use # response.close instead which takes care of closing all files. response.file_to_stream.close = response.close response = environ['wsgi.file_wrapper'](response.file_to_stream, response.block_size) return response
我们可以看到,WSGIHandler的**call**方法有两个参数environ和start_response,并返回 response
-
wsgi中间件
那么middleware是一种运行在server和application中间的应用(一般都是Python应用)。middleware同时具备server和application角色,对于server来说,它是一个application;对于application来说,它是一个server。
通常,中间件的存在对于接口的“服务器/网关”和“应用程序/框架”双方都是透明的,并且不需要任何特殊支持。希望将中间件合并到应用程序中的用户只需将中间件组件提供给服务器(就好像它是一个应用程序一样),并配置中间件组件来调用该应用程序,就好像中间件组件是一个服务器一样。当然,中间件包装的“应用程序”实际上可能是包装另一个应用程序的另一个中间件组件,依此类推,从而创建了所谓的“中间件堆栈”。
参考:中间件:兼顾双方的组件
uWSGI
2.1 uWSGI是什么
-
uWSGI是一个服务器,实现了uwsgi协议、wsgi协议和http协议。
-
是uWSGI独有的协议,uWSGI服务器使用的本地协议
它是一个二进制协议,可以携带任何类型的数据。一个uwsgi分组的头4个字节描述了这个分组包含的数据类型。
WSGI,uwsgi, uWSGI: 实现过程图解:
2.2 uWSGI下载
-
去官网下载最新版(官方教程和相关技术文章都是以最新版编写的)
https://uwsgi-docs.readthedocs.io/en/latest/Download.html页面,下载
Stable/LTS
版本的源文件 -
解压、安装
tar -xzvf uwsgi.tar.gz # 进入解压目录 sudo python3 setup.py install
-
运行一下uWSGI
注意:如果你在虚拟环境运行你的项目,你必须在虚拟环境中安装uWSGI
2.3 uWSGI简单使用
-
第一个WSGI应用
将下面代码写入
first.py
: 我们将其作为wsgi协议的applicationdef application(env, start_response): start_response('200 OK', [('Content-Type','text/html')]) return [b"H&W,goodbye!"]
uWSGI作为wsgi协议的server,调用这个application,将请求传入。
uwsgi --http 127.0.0.1:9090 --wsgi-file first.py
然后浏览器访问这个ip+端口:OK!
-
添加并发和监控
uWSGI启动一个单一的进程和一个单一的线程,但是想增加并发性。
使用 --process 选项增加进程 使用 --threads选项增加线程
uwsgi --http :9090 --wsgi-file first.py --master --processes 4 --threads 2 --stats 127.0.0.1:9191
生成4个进程(每个进程2个线程),一个master进程 (在Inc死掉的时候会生成它们) 和HTTP路由器 (见前面)。
对你的应用进行几次请求,然后telnet到端口9191,你会获得大量有趣的信息。你可能想要使用”uwsgitop” (仅需
pip install
来安装它),这是一个类似于top的工具,用来监控实例。 -
将其放在Nginx之后
通常将nginx作为服务器最前端,如果是静态请求,nginx自己处理,如果是动态请求,nginx将动态请求通过 uWSGI传给应用程序。
一个常用的nginx配置如下:
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
}使用命令行运行uWSGI:
uwsgi --socket 127.0.0.1:3031 --wsgi-file foobar.py --master --processes 4 --threads 2 --stats 127.0.0.1:9191
已经移除了HTTP路由器,因为我们的“worker” (被分配给uWSGI的进程) 本地使用uwsgi协议。
命令行比较繁琐,我们是用配置文件运行
uwsgi配置文件-uWSGI支持多种配置风格,比如 xml,ini,json 等等都可以。下面使用ini。
[uwsgi] socket = 127.0.0.1:3031 chdir = /home/foobar/myproject/ wsgi-file = myproject/wsgi.py processes = 4 threads = 2 stats = 127.0.0.1:9191
运行配置文件:
uwsgi yourfile.ini
列出比较常用的配置参数
chdir=/xxx/xxx # 指定项目目录 home=/xxx/xxx # 指定虚拟环境变量 wsgi-file=xxx # 指定加载WSGI文件 socket=xxx # 指定uwsgi的客户端将要连接的socket的路径(使用UNIX socket的情况)或者地址(使用网络地址的情况)。 callable=xxx # uWSGI加载的模块中哪个变量将被调用 master=true # 指定启动主进程 processes=4 # 设置工作进程的数量 threads=2 # 设置每个工作进程的线程数 vacuum=true # 当服务器退出时自动删除unix socket文件和pid文件 logfile-chmod=644 # 指定日志文件的权限 daemonize=%(chdir)/xxx.log # 进程在后台运行,并将日志打印到指定文件 pidfile=%(chdir)/xxx.pid # 在失去权限前,将主进程pid写到指定的文件 uid=xxx # uWSGI服务器运行时的用户id gid=xxx # uWSGI服务器运行时的用户组id procname-prefix-spaced=xxx # 指定工作进程名称的前缀
更多配置参数看官方文档
2.4 uwsgi部署中遇到的问题
-
uwsgi启动次数太多问题
问题描述:
打开nginx端口,一直显示 Internal Server Error
打开uwsgi的错误日志:
*** Starting uWSGI 2.0.19.1 (64bit) on [Tue Mar 30 15:48:09 2021] ***
compiled with version: 9.3.0 on 02 March 2021 12:41:15
os: Linux-3.10.0-957.el7.x86_64 #1 SMP Thu Nov 8 23:39:32 UTC 2018
nodename: bjxg-bd-slave03
machine: x86_64
clock source: unix
pcre jit disabled
detected number of CPU cores: 48
current working directory: /data/tornado/manage_project/business_management_system
detected binary path: /data/anaconda3/envs/jianhui/bin/uwsgi
chdir() to /data/tornado/manage_project/business_management_system
your processes number limit is 4096
your memory page size is 4096 bytes
detected max file descriptor number: 102400
lock engine: pthread robust mutexes
thunder lock: disabled (you can enable it with --thunder-lock)
probably another instance of uWSGI is running on the same address (127.0.0.1:5556).
bind(): Address already in use [core/socket.c line 769]
— no python application found, check your startup logs for errors —
[pid: 352529|app: -1|req: -1/40] 43.228.38.242 () {42 vars in 842 bytes} [Tue Mar 30 07:48:18 2021] GET /list/ => generated 21 bytes in 0 msecs (HTTP/1.1 500) 2 headers in 83 bytes (0 switches on core 0)
— no python application found, check your startup logs for errors —
[pid: 352529|app: -1|req: -1/41] 43.228.38.242 () {42 vars in 842 bytes} [Tue Mar 30 07:48:19 2021] GET /list/ => generated 21 bytes in 0 msecs (HTTP/1.1 500) 2 headers in 83 bytes (0 switches on core 0)重点在probably another instance of uWSGI is running on the same address (127.0.0.1:5556).
这句日志的直白的翻译出来就是:已经有uWSGI 实例运行在 这个地址上了
解决方法:
杀死所有在5556端口上的进程,然后重新启动uWSGI
sudo fuser -k 5556/tcp uwsgi [配置文件]
uwsgi的日志很重要,一定要重视!!!
-
uwsgi安装启动 错误描述
uwsgi
: error while loading shared libraries:libssl.so.1.1
: cannot open shared object file: No such file or directory解决方法
-
cp /data/anaconda3/lib/libssl.so.1.1 /lib64/
—方法1 -
ln -s /data/anaconda3/lib/libssl.so.1.1 /lib64/
—方法2 -
上述两个方法都可以,但是
lib64
文件夹没有相关权限,针对这种情况,解决方法如下:- 方法一:用anaconda创建虚拟环境并且用
conda install
安装uWSGI,这样下载的uWSGI没有问题 - 方法二:用
gunicorn
部署服务
- 方法一:用anaconda创建虚拟环境并且用
-
2.5 http和socket(UNIX以及TCP)的不同
Nginx
可以参考的文章
3.1 下载
-
首先更新软件源
可能出现 vim中删除键无法使用的情况,解决方法如下:
一、vi编辑模式下Backspace无法退格删除
命令模式下输入:set nocp
二、vi编辑模式下上下左右出现字母
sudo vi /etc/vim/vimrc.tiny
修改set compatible 为 set nocompatible 设置是否兼容
添加 set backspace=2 设置 backspace可以删除任意字符
解决方案:
1、使用国内的软件源在软件下载速度方面相对比较快,找到软件源文件
sources.list
的位置
2、在更新软件源之前建议先把原来的软件源文件进行备份,避免更改错误造成无法恢复。
3、备份完成后,打开软件源文件进行更改,下图是把软件源修改为网易软件源。修改完成后保存即可。
5、修改完成后使用
sudo apt-get update
命令进行更新软件源即可
- 下载安装
nginx
sudo add-apt-repository ppa:nginx/stable
apt-get update
apt-get install nginx
使用 service --status-all
命令查看一下:
[ + ] network-manager
[ + ] networking
[ + ] nginx
[ + ] ondemand
[ - ] plymouth
[ - ] plymouth-log
......
看到带+号的nginx
,表示一切OK
-
查看
-
查看
linux
对应的ip
-
使用同一局域网的主机,通过浏览器访问 这个
ip
,如果能看到下面这个,Nginx
服务正常。
-
3.2 基本使用
3.2.1 upstream
-
定义一组服务器,这些服务器可以监听不同的端口,而且监听在TCP和UNIX域套接字的服务器可以混用。
-
分配方式
-
轮询
请求按照时间顺序轮流分配到不同的后端服务器
-
weight权重
指定轮询比率,weight和访问几率成正比。
upstream backend { server backend1.example.com weight=5; server 127.0.0.1:8080 max_fails=3 fail_timeout=30s; server unix:/tmp/backend3; }
上面的例子,如果有七个请求,前五个发送到第一个服务器,第六和第七分别发送到第二个和第三个服务器。
-
ip_hash
每个请求按照访问ip(即Nginx的前置服务器或者客户端IP)的hash结果分配,这样每个访客会固定访问一个后端服务器,可以解决session一致问题。
upstream backend { ip_hash; server 192.168.1.101:7777; server 192.168.1.102:8888; server 192.168.1.103:9999; }
-
fair
按照后端响应的时间来分配请求,响应时间短的后端服务器有限分配请求。
upstream backend { server 192.168.1.101; server 192.168.1.102; server 192.168.1.103; fair; }
-
-
语法
语法为 server address [parameters]
参数说明:
server:关键字,必选
address:主机名、域名、ip或unix socket,也可指定端口号,必选
parameters:可选参数,如下:
1.down 当前server已停用
2.backup:备用服务器,其余服务器挂掉或者很忙的时候才分配请求
3.weight:表示当前server负载权重,权重越大被请求的概率越大。默认是1
4.max_fails和fail_timeout一般会关联使用,如果某台server在fail_timeout时间内出现了max_fails次连接失败,那么Nginx会认为其已经挂掉了,从而在fail_timeout时间内不再去请求它,fail_timeout默认是10s,max_fails默认是1,即默认情况是只要发生错误就认为服务器挂掉了,如果将max_fails设置为0,则表示取消这项检查。
e.g.:
upstream backend { server backend1.example.com weight=5; server 127.0.0.1:8080 max_fails=3 fail_timeout=30s; server unix:/tmp/backend3; }
3.2.2 Server
-
参考文章
3.2.3
管理系统后端的nginx文件
upstream business-front{
server 172.25.102.72:17401;
fair;
}
upstream business-backend{
server 172.25.102.72:17411;
fair;
}
server {
listen 80;
server_name admin.business.infinities.com.cn;
access_log /data/logs/admin.business.infinities.com.cn.access.log;
error_log /data/logs/admin.business.infinities.com.cn.error.log;
root /data/tornado/manage_project/business_management_system;
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://business-front/;
}
location /api/ {
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://business-backend/;
}
}
docker-简单使用
镜像和容器的不同
-
镜像
是一个静态文件,一个独立的文件系统,有一层层的只读层构成
多个只读层叠在一起,多个只读层通过指针连接在一起,linux将这些层封装合并起来,提供一个统一的视角。
统一文件系统(union file system)技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。
-
容器
容器是运行中的镜像,它比镜像多了一个可读可写层
docker 容器=镜像+可读可写层
-
镜像和容器的不同
镜像中包含了应用程序运行的所必须的操作系统和应用文件,但不包含内核
容器比镜像多了一层可读可写层
运行中的容器被定义为一个统一的文件系统加上隔离的进程空间和包含其中的进程
-
容器创建的指令
第一种:
1.docker create
为镜像添加了一个可读写层,构成了一个新的容器,但是这个容器没有运行
2.docker start <容器id>
为容器创建了一个进程隔离空间
第二种:
docker run [OPTIONS]
创建并启动一个容器
**-d, --detach=false, 指定容器运行于前台还是后台,如果是后台 -d 或者 -d=true即可 **
-i, --interactive=false, 打开STDIN,用于控制台交互
-t, --tty=false, 分配tty设备,该可以支持终端登录,默认为false
-u, --user="", 指定容器的用户
-a, --attach=[], 登录容器(必须是以docker run -d启动的容器)
-w, --workdir="", 指定容器的工作目录
-c, --cpu-shares=0, 设置容器CPU权重,在CPU共享场景使用
-e, --env=[], 指定环境变量,容器中可以使用该环境变量
-m, --memory="", 指定容器的内存上限
-P, --publish-all=false, 指定容器暴露的端口 e.g:-p 17411:80 **
-p, --publish=[], 指定容器暴露的端口
-h, --hostname="", 指定容器的主机名
-v, --volume=[], 给容器挂载存储卷,挂载到容器的某个目录 e.g.**-v /data/tornado/manage_project/business_management_system:/code **
–volumes-from=[], 给容器挂载其他容器上的卷,挂载到容器的某个目录
–cap-add=[], 添加权限,权限清单详见:http://linux.die.net/man/7/capabilities
–cap-drop=[], 删除权限,权限清单详见:http://linux.die.net/man/7/capabilities
–cidfile="", 运行容器后,在指定文件中写入容器PID值,一种典型的监控系统用法
–cpuset="", 设置容器可以使用哪些CPU,此参数可以用来容器独占CPU
–device=[], 添加主机设备给容器,相当于设备直通
–dns=[], 指定容器的dns服务器
–dns-search=[], 指定容器的dns搜索域名,写入到容器的/etc/resolv.conf文件
–entrypoint="", 覆盖image的入口点
–env-file=[], 指定环境变量文件,文件格式为每行一个环境变量
1.创建镜像和容器
1.用Dockerfile 来创建镜像
参考文档
用docker build
来创建一个新的镜像。
1.docker build 构建镜像
docker是一个C/S架构的应用,分为Docker客户端(docker命令)和Docker引擎(dockerd守护进程),
Docker 的引擎提供了一组 REST API,客户端通过这组API与Docker引擎交互,从而完成各种任务
首先需要了解的一点 docker build命令构建镜像,其实并非在本地构建,而是在服务端,也就是docker引擎中构
我们构建镜像的时候,经常会将一些文件复制进镜像,比如COPY 、ADD命令,而镜像在**服务端(引擎)**中构建,如何让服务端获取本地文件呢。
我们拿下面这条命令举例:
helloworld-app
├── Dockerfile
└── docker
├── app-1.0-SNAPSHOT.jar
├── hello.txt
└── html
└── index.html
我们在文件夹 helloworld-app中运行:
docker build -t hello-app:1.0 docker
我们指定docker 文件夹 路径为上下文的路径,这时docker build会将这个路径下的所有内容打包,然后上传给Docker引擎,这样Docker引擎收到这个上下文包,然后 Dockerfile 如果COPY 的话是从这个上下文包中复制文件到服务器。
如果在 Dockerfile
中这么写:
COPY hello.txt .
这并不是要复制执行 docker build
命令所在的目录下的 hello.txt
,也不是复制 Dockerfile
所在目录下的 hello.txt
,而是复制 上下文(context) 目录下的 hello.txt
。
但是上面构建镜像的命令会出错:
unable to prepare context: unable to evaluate symlinks in Dockerfile path: lstat /Users/haohao/opensource/helloworld-app/docker/Dockerfile: no such file or directory
找不到Dockerfile文件
这里我们要明白的一点,docker build 构建镜像,docker客户端默认从 构建上下文的路径找名字为Dockerfile的构建文件,实际的Dockerfile文件不在上下文路径docker文件夹下面,所以出错。
我们可以指定 Dockerfile文件 :参数 -f <指定的Dockerfile文件>
$ docker build -f Dockerfile -t hello-app:1.0 docker
Sending build context to Docker daemon 96.61MB
Step 1/3 : FROM busybox
---> d8233ab899d4
Step 2/3 : COPY hello.txt .
---> 3305fc373120
Step 3/3 : COPY html/index.html .
---> efdefc4e6eb2
Successfully built efdefc4e6eb2
Successfully tagged hello-app:1.0
上下文件路径不要放一些用不到的内容,这样会导致构建镜像缓慢且易失败
- 首先你得创建一个Dockerfile
2 .Dockerfile的基本语法
-
使用 **#**来注释
-
FROM
指令告诉 Docker 使用哪个镜像作为基础
这必须是第一条指令。
有可以拿来使用的服务类的镜像:如 nginx、redis、mongo和mysql等
有一些方便开发、构建、运行各种语言应用的镜像:如node、python、openjdk等
e.g.
FROM python:3.7.10
-
RUN
-
run 命令是在当前镜像的基础上执行指定命令,然后提交为新的镜像
-
用于执行后面跟着的命令行命令。有以下两种格式
1.shell格式
RUN <命令行命令>
e.g.
RUN pip install -r /code/requirements.txt -i https://mirrors.aliyun.com/pypi/simple
像是直接在命令行中输入命令一样
2.exec 格式
RUN [“可执行文件”, “参数1”, “参数2”]
像是函数式的调用
-
每次执行run都相当于创建了一个新的镜像,如果创建的镜像过多,会显得臃肿,且有层数限制,不能超过127层。
所以 考虑将类似功能的 run 命令 用 && 连接起来,并用 **\ **换行:
e.g:
FROM debian:jessie
RUN buildDeps=‘gcc libc6-dev make’
&& apt-get update
&& apt-get install -y $buildDeps
&& wget -O redis.tar.gz “http://download.redis.io/releases/redis-3.2.5.tar.gz”
&& mkdir -p /usr/src/redis
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
&& make -C /usr/src/redis
&& make -C /usr/src/redis install
&& rm -rf /var/lib/apt/lists/*
&& rm redis.tar.gz
&& rm -r /usr/src/redis
&& apt-get purge -y --auto-remove $buildDeps
-
-
COPY
复制指令,从上下文目录复制文件到容器内指定路径
格式:
COPY [–chown=:] <源路径1>… <目标路径>
[–chown=:]:可选参数,用户改变复制到容器内文件的拥有者和属组。
<源路径>:源文件或者源目录,可以使用通配符表达式
COPY hom* /mydir/
注意:是从上下文目录中复制文件到 容器里(无论此前的本地的上下文路径是什么,此时容器内的上下文路径都是 . )
-
CMD
容器是进程,那么在启动这个进程(容器)时,需要指定运行得程序和参数。
我们通过docker run命令创建并启动一个容器得时候,可以在命令最后面指定容器主进程。
其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。
例如:
docker run -i -t ubunu /bin/bash //表示容器启动时立即在容器内打开一个shell终端 docker run ubuntu /bin/ps //表示容器启动后立即运行 /bin/ps命令,显示容器的当前进程。
除了这种方式,我们可以在Dockfile文件通过CMD命令指定容器启动时要执行得命令:
有以下两种格式:
1.shell 格式
CMD <命令>
e.g:
CMD sh deploy.sh
2.exec 格式
CMD [“可执行文件”,“参数1”,“参数2”…]
e.g.
CMD [“bash”, “/code/entrypoint.sh”]
exec格式在解析得时候会被解析成JSON,此时会把 CMD后面跟着得程序作为1号进程(1号进程详细解释看1号进程)
shell格式会自动在前面加上 sh -c
比如:
CMD echo $HOME
在实际执行中,会将其变更为:
CMD [ "sh", "-c", "echo $HOME" ]
但是这种方式不够直观,如果我不想用shell进行执行,或者如果容器中没有 shell程序,docker容器就不能运行。
A better option is to use the exec form of the ENTRYPOINT/CMD instructions which looks like this
所以推荐用exec方式
-
ENTRYPOINT 入口点
ENTRYPOINT命令和CMD命令一样,都是指定容器内启动程序和参数。ENTRYPOINT和CMD一样,也有两种格式(exec格式和shell格式)
ENTRYPOINT在docker容器创建启动(docker run)得时候也可以被
替换,需要通过
docker run
得参数 --entrypoint来指定。当指定了
ENTRYPOINT
后,CMD
的含义就发生了改变,不再是直接的运行其命令,而是将CMD
的内容作为参数传给ENTRYPOINT
指令,换句话说实际执行时,将变为:<ENTRYPOINT> "<CMD>"
那么有了
CMD
后,为什么还要有ENTRYPOINT
呢?这种<ENTRYPOINT> "<CMD>"
有什么好处么?让我们来看几个场景。#场景一:让镜像变成像命令一样使用
假设我们需要一个得知自己当前公网 IP 的镜像,那么可以先用
CMD
来实现:FROM ubuntu:18.04 RUN apt-get update \ && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/* CMD [ "curl", "-s", "http://myip.ipip.net" ]
假如我们使用
docker build -t myip .
来构建镜像的话,如果我们需要查询当前公网 IP,只需要执行:$ docker run myip 当前 IP:61.148.226.66 来自:北京市 联通
嗯,这么看起来好像可以直接把镜像当做命令使用了,不过命令总有参数,如果我们希望加参数呢?比如从上面的
CMD
中可以看到实质的命令是curl
,那么如果我们希望显示 HTTP 头信息,就需要加上-i
参数。那么我们可以直接加-i
参数给docker run myip
么?$ docker run myip -i docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".
我们可以看到可执行文件找不到的报错,
executable file not found
。之前我们说过,跟在镜像名后面的是command
,运行时会替换CMD
的默认值。因此这里的-i
替换了原来的CMD
,而不是添加在原来的curl -s http://myip.ipip.net
后面。而-i
根本不是命令,所以自然找不到。那么如果我们希望加入
-i
这参数,我们就必须重新完整的输入这个命令:$ docker run myip curl -s http://myip.ipip.net -i
这显然不是很好的解决方案,而使用
ENTRYPOINT
就可以解决这个问题。现在我们重新用ENTRYPOINT
来实现这个镜像:FROM ubuntu:18.04 RUN apt-get update \ && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/* ENTRYPOINT [ "curl", "-s", "http://myip.ipip.net" ]
这次我们再来尝试直接使用
docker run myip -i
:$ docker run myip 当前 IP:61.148.226.66 来自:北京市 联通 $ docker run myip -i HTTP/1.1 200 OK Server: nginx/1.8.0 Date: Tue, 22 Nov 2016 05:12:40 GMT Content-Type: text/html; charset=UTF-8 Vary: Accept-Encoding X-Powered-By: PHP/5.6.24-1~dotdeb+7.1 X-Cache: MISS from cache-2 X-Cache-Lookup: MISS from cache-2:80 X-Cache: MISS from proxy-2_6 Transfer-Encoding: chunked Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006 Connection: keep-alive 当前 IP:61.148.226.66 来自:北京市 联通
可以看到,这次成功了。这是因为当存在
ENTRYPOINT
后,CMD
的内容将会作为参数传给ENTRYPOINT
,而这里-i
就是新的CMD
,因此会作为参数传给curl
,从而达到了我们预期的效果。#场景二:应用运行前的准备工作
启动容器就是启动主进程,但有些时候,启动主进程前,需要一些准备工作。
比如
mysql
类的数据库,可能需要一些数据库配置、初始化的工作,这些工作要在最终的 mysql 服务器运行之前解决。此外,可能希望避免使用
root
用户去启动服务,从而提高安全性,而在启动服务前还需要以root
身份执行一些必要的准备工作,最后切换到服务用户身份启动服务。或者除了服务外,其它命令依旧可以使用root
身份执行,方便调试等。这些准备工作是和容器
CMD
无关的,无论CMD
为什么,都需要事先进行一个预处理的工作。这种情况下,可以写一个脚本,然后放入ENTRYPOINT
中去执行,而这个脚本会将接到的参数(也就是<CMD>
)作为命令,在脚本最后执行。比如官方镜像redis
中就是这么做的:FROM alpine:3.4 ... RUN addgroup -S redis && adduser -S -G redis redis ... ENTRYPOINT ["docker-entrypoint.sh"] EXPOSE 6379 CMD [ "redis-server" ]
可以看到其中为了 redis 服务创建了 redis 用户,并在最后指定了
ENTRYPOINT
为docker-entrypoint.sh
脚本。#!/bin/sh ... # allow the container to be started with `--user` if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then find . \! -user redis -exec chown redis '{}' + exec gosu redis "$0" "$@" fi exec "$@"
该脚本的内容就是根据
CMD
的内容来判断,如果是redis-server
的话,则切换到redis
用户身份启动服务器,否则依旧使用root
身份执行。比如:$ docker run -it redis id uid=0(root) gid=0(root) groups=0(root)
-
ENV
ENV 定义镜像的环境变量,格式如下:
第一种:
ENV 变量名1 变量值1 变量名2 变量值2
第二种:
ENV 变量名1=变量值1 变量名2=变量值2 变量名3=变量值3
e.g.
ENV VERSION=1.0 DEBUG=on\ NAME="Happy Feet"
后面得下列指令可以支持环境变量展开:
ADD
、COPY
、ENV
、EXPOSE
、FROM
、LABEL
、USER
、WORKDIR
、VOLUME
、STOPSIGNAL
、ONBUILD
、RUN
都可以使用前面设置得环境变量docker run时ENV的变量会加载到容器内部。
可以在
Dockfile
中指定,也可以 通过在docker run
加 --env来指定 -
ARG
ARG和ENV一样,都是设置环境变量,区别在与ARG设置的环境变量只在 创建 镜像时存在,在容器内不存在。
还需要注意的有两点:
-
默认值
如果 只是 单单的定义变量 但是没有定义值,如
ARG 变量值
此时 默认变量值 为 空字符串("")
-
覆盖
我们在构建 镜像时,可以在
docker build
命令后面加 参数 --build-arg 变量名=变量值 来覆盖 在Dockerfile中定义的 ARG 参数值,但是, 如果没有在Dockerfile用ARG 定义变量,在构建镜像时(docker build)时,就会出现警告:[Warning] One or more build-args [build_env] were not consumed
-
-
VOLUME
问题:
容器是基于镜像创建的,容器比镜像多了一层可读可写层,容器中进程产生的数据都保存在这个可读可写层,一旦容器关闭,数据就会丢失。
针对上面的问题,docker
2.查询、删除、进入
-
查询镜像
docker images
-
查询容器
docker ps -a
-
停止运行中的容器
docker stop [容器ID或容器名]
-
重启容器
容器ID或容器名 :不管容器是否启动,直接重启容器
-
进入到容器中
docker exec -it <容器的名字或者容器id> /bin/bash
-
删除容器
docker rm <容器ID>
-
删除镜像
docker rmi <镜像ID>
-
注意
-
删除前需要保证容器是停止的 stop
-
需要注意删除镜像和容器的命令不一样。 docker rmi ID ,其中 容器(rm) 和 镜像(rmi)****
-
顺序需要先删除容器
-
3.安装程序包
-
同步
**apt-get update **#同步 /etc/apt/sources.list 和 /etc/apt/sources.list.d 中列出的源的索引,获取到最新的软件包
-
安装
apt-get install <程序的包名>
screen-简单使用
用途
用 xshell 连接 linux,在linux上起了一个服务,这个服务不能中断,我不能一直开着电脑啊!
GNU Screen可以看作是窗口管理器的命令行界面版本。它提供了统一的管理多个会话的界面和相应的功能。
-
会话恢复
只要Screen本身没有终止,在其内部运行的会话都可以恢复。这一点对于远程登录的用户特别有用——即使网络连接中断,用户也不会失去对已经打开的命令行会话的控制。只要再次登录到主机上执行screen -r就可以恢复会话的运行。同样在暂时离开的时候,也可以执行分离命令detach,在保证里面的程序正常运行的情况下让Screen挂起(切换到后台)。这一点和图形界面下的VNC很相似。
-
会话共享
Screen可以让一个或多个用户从不同终端多次登录一个会话,并共享会话的所有特性(比如可以看到完全相同的输出)。它同时提供了窗口访问权限的机制,可以对窗口进行密码保护。
基本使用
-
安装
docker 安装 screen:
**apt-get update **#同步 /etc/apt/sources.list 和 /etc/apt/sources.list.d 中列出的源的索引,获取到最新的软件包
apt-get install screen
-
启动一个会话
命令:screen -S <会话的名字> <需要执行的命令>
e.g.
screen -S busssiness_test python manage.py runserver 0.0.0.0:5556
-
离开会话
按住键盘上的ctrl键,然后依次按a和d,这是回到主回话,这时可以创建其他回话或者离开主回话
-
恢复创建的会话
-
如果记得回话的名字
screen -r <回话的名字>
如果不记得,可以使用命令 查看已经创建的回话:
screen -ls
root@f55bf55a5951:/code# screen -ls There is a screen on: 593.bus_test (04/21/21 15:40:20) (Detached) 1 Socket in /run/screen/S-root
593是回话id,可以直接 screen -r <会话id>进行回复
-
如果不记得回话的名字且你只创建了一个回话
screen -r
-
-
查看已经创建的回话
screen -ls
-
恢复问题
有时候恢复screen时出现 **There is no screen to be resumed matching **
这种情况,输入命令:
screen -d ****
然后再使用恢复命令恢复就ok
-
退出screen
exit
-
其他命令
Ctrl + a,d #暂离当前会话
Ctrl + a,c #在当前screen会话中创建一个子会话
Ctrl + a,w #子会话列表
Ctrl + a,p #上一个子会话
Ctrl + a,n #下一个子会话
Ctrl + a,0-9 #在第0窗口至第9子会话间切换
shell脚本简单使用
1.shell变量
在 Bash shell中,每一个变量的值都是字符串,无论你给变量赋值时有没有使用引号,值都会以字符串的形式存储。
1.变量操作
-
创建普通变量
1.name=zhoujianhui
2.name=‘zhoujianhui’
3.name=“zhoujianhui”
三种方式创建变量都可以,注意等号两边不要有空格
-
创建只可变函数体中使用的局部变量:local name=“test” (local修饰的变量只能在函数内使用)
-
使用变量
echo $name
echo ${name}
因为第二种限定边界,在变量中使用比较好,例如:
第一种:
脚本aa
name=zhoujianhui echo "my name is $namedingding!"
执行语句:
$ sh aa my name is !
解释器会把$namedingding当成一个变量(其值为空)。
-
变量重新赋值
name=‘zhoujianfeng’
-
只读变量
name=“只读” -> readonly name
e.g.
# bigdataservice @ bjxg-bd-slave03 in /data/tornado/manage_project $ name=zhoujianfeng # bigdataservice @ bjxg-bd-slave03 in /data/tornado/manage_project $ readonly name # bigdataservice @ bjxg-bd-slave03 in /data/tornado/manage_project $ name='nishishui' -bash: name: readonly variable
-
删除变量
unset name;
supervisor的简单使用
supervisor 介绍
supervisor是用python开发的一套进程管理程序,它可以将命令行进程转化成自己的子进程(后台守护进程)
,并且监控子进程的状态,在子进程挂掉的时候将其重启或者报警。
安装
1.Redhat、Centos下安装
yum install supervisor
2.Debian/Ubuntu下安装
apt-get install supervisor
3.pip 安装 supervisor
pip install supervisor
2.字
配置
1.主配置文件的生成
如果用apt-get方式下载,则自动生成主配置文件 supervisord.conf,跳过这一步,如果使用pip install方式安装,则需要手动生成:
1.生成主配置文件
cd /etc
mkdir supervisor #创建supervisor目录
cd /etc/supervisor
echo_supervisord_conf > /etc/supervisor/supervisord.conf #生成配置文件
mkdir conf.d #生成放置具体配置文件的目录
2.修改与具体配置
手动生成 主配置文件后,打开主配置文件 supervisord.conf这个文件:
在最后面添加:
[include]
files = /etc/supervisor/conf.d/*.conf
在启动supervisord时,它会加载conf.d文件夹中的所有.conf配置文件(conf.d文件夹下有每个的配置文件对应一个需要启动和监控的守护进程)
2.主配置文件具体参数
root@0697f6cfb9a4:/code# whereis supervisor #查看supervisor文件在哪里
supervisor: /etc/supervisor /usr/share/man/man1/supervisor.1.gz
root@0697f6cfb9a4:/code# ls /etc/supervisor/ #supervisor的配置文件所在地
conf.d supervisord.conf
root@0697f6cfb9a4:/code# cat /etc/supervisor/supervisord.conf #supervisor的配置文件
supervisord.conf的内容:
; supervisor config file
[unix_http_server]
file=/var/run/supervisor.sock ; (the path to the socket file)
chmod=0700 ; sockef file mode (default 0700)[supervisord]
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
childlogdir=/var/log/supervisor ; (‘AUTO’ child log dir, default $TEMP); the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL for a unix socket; The [include] section can just contain the “files” setting. This
; setting can list multiple files (separated by whitespace or
; newlines). It can also contain wildcards. The filenames are
; interpreted as relative to this file. Included files cannot
; include files themselves.[include]
files = /etc/supervisor/conf.d/*.conf
-
主配置文件相关参数
以上配置文件用到几个部分:
- [unix_http_server]:这部分设置HTTP服务器监听的UNIX domain socket
- file: 指向UNIX domain socket,即file=/var/run/supervisor.sock
- chmod:启动时改变supervisor.sock的权限
- [supervisord]:与supervisord有关的全局配置需要在这部分设置
- logfile: 指向记录supervisord进程的log文件
- pidfile:pidfile保存子进程的路径
- childlogdir:子进程log目录设为AUTO的log目录
- [supervisorctl]:
- serverurl:进入supervisord的URL, 对于UNIX domain sockets, 应设为 unix:///absolute/path/to/file.sock
- [include]:如果配置文件包含该部分,则该部分必须包含一个files键:
- files:包含一个或多个文件,这里包含了/etc/supervisor/conf.d/目录下所有的.conf文件,可以在该目录下增加我们自己的配置文件,在该配置文件中增加[program:x]部分,用来运行我们自己的程序,如下:
- [program:x]:配置文件必须包括至少一个program,x是program名称,必须写上,不能为空
- command:包含一个命令,当这个program启动时执行
- directory:执行子进程时supervisord暂时切换到该目录
- user:账户名
- startsecs:进程从STARING状态转换到RUNNING状态program所需要保持运行的时间(单位:秒)
- redirect_stderr:如果是true,则进程的stderr输出被发送回其stdout文件描述符上的supervisord
- stdout_logfile:将进程stdout输出到指定文件
- stdout_logfile_maxbytes:stdout_logfile指定日志文件最大字节数,默认为50MB,可以加KB、MB或GB等单位
- stdout_logfile_backups:要保存的stdout_logfile备份的数量
- [unix_http_server]:这部分设置HTTP服务器监听的UNIX domain socket
常用命令
-
启动主进程命令
supervisord -c /etc/supervisord.conf #初始启动 #初始启动
-
supervisor命令
**<项目的名字> **在每个子进程的配置文件首行指定
supervisorctl status //查看所有进程的状态
supervisorctl stop <项目的名字> //停止es
supervisorctl start es <项目的名字> //启动es
supervisorctl restart <项目的名字> //重启es
supervisorctl update //配置文件修改后使用该命令加载新的配置
supervisorctl reload //重新启动配置中的所有程序
例子
因为项目有耗时任务,所以需要用celery进行异步程序执行,但是celery启动命令不能后台执行,需要用supervisor进行托管。
在/etc/supervisor/conf.d/文件夹下,新建 celery_worker.conf文件,文件内容如下:
[program:CeleryWork]
#CeleryWork 为程序的名称 x是program名称,必须写上,不能为空
command=celery -A business_management_system worker -l info
#需要执行的命令
directory=/code
#命令执行的目录
#environment=ASPNETCORE__ENVIRONMENT=Production
#环境变量
stopsignal=INT
autostart=true
#是否自启动
autorestart=true
#是否自动重启
startsecs=3
#自动重启时间间隔(s)
stderr_logfile=/code/celeryworker.err.log
#错误日志文件
stdout_logfile=/code/celeryworker.out.log
#输出日志文件