一.需求分析:
一发布系统申请上线
1.后端:
(1).apps/release/models.py:
python36env) [vagrant@CentOS devops]$ django-admin startapp release (python36env) [vagrant@CentOS devops]$ mv release apps/ (python36env) [vagrant@CentOS devops]$ python manage.py makemigrations release (python36env) [vagrant@CentOS devops]$ python manage.py migrate release
(2).release/views.py
from django.contrib.auth import get_user_model from rest_framework import viewsets, permissions, status from rest_framework.response import Response from rest_framework.pagination import PageNumberPagination from rest_framework import filters from rest_framework.authentication import BasicAuthentication, SessionAuthentication from rest_framework_jwt.authentication import JSONWebTokenAuthentication from .serializers import DeploySerializer from .models import Deploy from time import sleep User = get_user_model() class Pagination(PageNumberPagination): page_size = 10 page_size_query_param = 'page_size' page_query_param = "page" max_page_size = 100 class DeployViewset(viewsets.ModelViewSet): """ create: 申请上线 list: 获取上线列表 retrieve: 获取上线信息 update: 代码更新信息 delete: 取消上线 """ authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication, BasicAuthentication) permission_classes = (permissions.IsAuthenticated, permissions.DjangoModelPermissions) queryset = Deploy.objects.all() serializer_class = DeploySerializer pagination_class = Pagination filter_backends = (filters.SearchFilter, filters.OrderingFilter) search_fields = ('name',) ordering_fields = ('apply_time', 'deploy_time') def get_queryset(self): rate = self.request.GET.get('status', None) applicant = self.request.user role = applicant.groups.all().values('name') role_name = [r['name'] for r in role] queryset = super(DeployViewset, self).get_queryset() # 判断传来的status值判断是申请列表还是历史列表 if rate and int(rate) <= 2: queryset = queryset.filter(status__lte=2) elif rate and int(rate) > 2: queryset = queryset.filter(status__gte=2) else: pass # 判断登陆用户是否是管理员,是则显示所有项目,否则只显示自己的项目 if "sa" not in role_name: queryset = queryset.filter(applicant=applicant) return queryset # def partial_update(self, request, *args, **kwargs): # pk = kwargs.get("pk") # data = request.data # data['assign_to'] = request.user # print(data) # if int(data['status']) == 3: # print("1111") # jenkins = JenkinsApi() # number = jenkins.get_next_build_number(data['name']) # jenkins.build_job(data['name'], parameters={'tag': data['version']}) # sleep(30) # console_output = jenkins.get_build_console_output(data['name'], number) # data['console_output'] = console_output # Deploy.objects.filter(pk=pk).update(**data) # return Response(status=status.HTTP_204_NO_CONTENT)
(3).release/serializers.py
from rest_framework import serializers from django.contrib.auth import get_user_model from .models import Deploy User = get_user_model() class DeploySerializer(serializers.ModelSerializer): """ 工单序列化类 """ # 获取当前登陆用户,并将其赋值给数据库中对应的字段 applicant = serializers.HiddenField( default=serializers.CurrentUserDefault()) apply_time = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", read_only=True) complete_time = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", read_only=True) class Meta: model = Deploy fields = "__all__" def to_representation(self, instance): applicant_obj = instance.applicant reviewer_obj = instance.reviewer assign_to_obj = instance.assign_to status_value = instance.get_status_display() ret = super(DeploySerializer, self).to_representation(instance) ret['status'] = { "id": instance.status, "name": status_value } ret["applicant"] = { "id": applicant_obj.id, "name": applicant_obj.name }, ret["reviewer"] = { "id": reviewer_obj.id, "name": reviewer_obj.name }, if assign_to_obj: ret["assign_to"] = { "id": assign_to_obj.id, "name": assign_to_obj.name }, return ret
(4).release/route.py
from rest_framework.routers import DefaultRouter from .views import DeployViewset deploy_router = DefaultRouter() deploy_router.register(r'deploy', DeployViewset, basename="deploy")
(5).release/task.py
(6).devops/urls.py
from django.conf.urls import url, include from rest_framework_jwt.views import obtain_jwt_token from rest_framework.routers import DefaultRouter from rest_framework.documentation import include_docs_urls from groupUsers .views import GroupUsersViewset from users.router import router as user_router from workorder.router import workorder_router from devops.settings import STATIC_ROOT from release.router import deploy_router from groups.router import group_router from permissions.router import permission_router from groupUsers.router import groupuser_router from resources.router import router as resources_router router = DefaultRouter() router.registry.extend(user_router.registry) router.registry.extend(workorder_router.registry) router.registry.extend(group_router.registry) router.registry.extend(groupuser_router.registry) router.registry.extend(permission_router.registry) router.registry.extend(deploy_router.registry) urlpatterns = [ url(r'^api/', include(router.urls)), url(r'^', include('resources.urls')), url(r'^api-auth/', include('rest_framework.urls')), url(r'^docs/', include_docs_urls("51reboot接口文档")), url('^projects/', include('projects.urls', namespace='projects')), url(r'^api-token-auth/', obtain_jwt_token), ]
(7).apps/utils/jenkins_api.py
import gitlab from devops.settings import GITLAB_HTTP_URI, GITLAB_TOKEN gl = gitlab.Gitlab(GITLAB_HTTP_URI, GITLAB_TOKEN) def get_user_projects(request): """ 获取gitlab里所有的项目,和登录用户所拥有的项目,以及登录用户所拥有项目的项目成员 :return: [] """ user_projects = [] all_projects = gl.projects.list() print(request.user.username) print(all_projects) # 获取当前用户所有的项目 for project in all_projects: print(project) for member in project.members.list(): # if member.username == request.user.username: if member.username == "root": user_projects.append(project) print(user_projects) return user_projects def get_project_versions(project_id): """ 获取某个项目的版本号 :param project_id: :return: """ project = gl.projects.get(project_id) tags = project.tags.list() print(tags) return tags
效果如图:
2.前端
(1)src/api/release/release.js
import request from '@/utils/request' // 获取申请列表 export function getDeployList(params) { return request({ url: '/api/deploy/', method: 'get', params }) } // 申请项目上线 export function createDeploy(data) { return request({ url: '/api/deploy/', method: 'post', data }) } // 更新项目 export function updateDeploy(id, data) { return request({ url: '/api/deploy/' + id + '/', method: 'patch', data }) }
(2)src/router/index.js
{ path: '/release', component: Layout, name: '代码上线', meta: { title: '代码上线', icon: 'user' }, children: [ { path: 'apply', name: '申请上线', component: () => import('@/views/release/apply/index'), meta: { title: '申请上线', icon: 'user' } }, { path: 'list', name: '申请列表', component: () => import('@/views/release/list/index'), meta: { title: '申请列表', icon: 'tree' } }, { path: 'history', name: '上线列表', component: () => import('@/views/release/history/index'), meta: { title: '上线列表', icon: 'tree' } } ] },
(3)views/release/apply/index.vue
<template> <div class="apply"> <el-form ref="form" :model="form" :rules="rules" label-width="180px"> <el-form-item label="选择项目:" prop="name"> <el-select v-model="form.name" placeholder="请选择项目类型" style="width: 60%;" @change="getTag"> <el-option v-for="item in project_list" :key="item.index" :label="item.name" :value="item.name"/> </el-select> </el-form-item> <el-form-item label="项目版本:" prop="version"> <el-select v-model="form.version" value-key="label" placeholder="请选择项目版本" style="width: 60%;" @change="getTaginfo"> <el-option v-for="item in tag_list" :key="item.index" :label="item.name" :value="item.name"/> </el-select> </el-form-item> <el-form-item label="版本信息:" prop="info"> <el-input v-model="form.info" style="width: 60%;"/> </el-form-item> <el-form-item label="指派给:" prop="assign_to"> <el-select v-model="form.reviewer" filterable placeholder="请选择项目处理人" style="width: 60%;"> <el-option v-for="item in sa_list" :key="item.index" :label="item.name" :value="item.id"/> </el-select> </el-form-item> <el-form-item label="上线详情:" prop="detail"> <el-input :rows="8" v-model="form.detail" type="textarea" style="width: 60%;"/> </el-form-item> <el-form-item> <el-button type="primary" @click="onSubmit">申请上线</el-button> </el-form-item> </el-form> </div> </template> <script> import { getGroupMemberList } from '@/api/group' import { getProjectList, getProjectTag } from '@/api/project/project' import { createDeploy } from '@/api/release/release' export default { data() { return { form: { name: '', version: '', info: '', detail: '', reviewer: '' }, rules: { name: [ { required: true, message: '请输入项目名称', trigger: 'blur' } ], version: [ { required: true, message: '请输入项目版本', trigger: 'blur' } ], info: [ { required: true, message: '请输人项目内容', trigger: 'blur' } ], reviewer: [ { required: true, message: '请输人项目执行人', trigger: 'blur' } ] }, sa_list: [], project_list: [], tag_list: [], state: 0 } }, watch: { state() { getGroupMemberList(6).then(res => { this.sa_list = res.members console.log(this.sa_list) }) getProjectList().then(res => { this.project_list = res console.log(this.project_list) }) } }, created() { this.state = 1 }, methods: { /* 获取某个项目的tag列表*/ getTag(name) { let obj = {} obj = this.project_list.find((item) => { return item.name === name }) const params = { 'project_id': obj.id } getProjectTag(params).then(res => { this.tag_list = res console.log(this.tag_list) }) }, /* 跟进tag名获取message */ getTaginfo(name) { let obj = {} obj = this.tag_list.find((item) => { return item.name === name }) console.log(obj) this.form.info = obj.info }, /* 申请上线 */ onSubmit() { this.$refs.form.validate((valid) => { if (!valid) { return } const params = Object.assign({}, this.form) console.log(params) createDeploy(params).then(res => { this.$message({ message: '申请上线成功', type: 'success' }) this.$router.push({ path: '/release/list' }) }) }) } } } </script> <style scoped> .line{ text-align: center; } .apply{ margin-top:2cm; } </style>
(4)views/release/list/form.vue
<template> <div class="deploy-form"> <el-form ref="form" :model="form" :rules="rules" label-width="100px" class="demo-form"> <el-form-item label="项目名称" prop="name"> <el-input v-model="form.name" readonly/> </el-form-item> <el-form-item label="项目版本" prop="version"> <el-input v-model="form.version" readonly/> </el-form-item> <el-form-item label="项目描述" prop="info"> <el-input v-model="form.info" readonly/> </el-form-item> <el-form-item label="项目详情" prop="detail"> <el-input v-model="form.detail" type="textarea" rows="8" readonly/> </el-form-item> <el-form-item> <div class="btn-wrapper"> <el-button size="small" type="primary" @click="submitForm">下一步</el-button> </div> </el-form-item> </el-form> </div> </template> <script> export default { name: 'DeployForm', props: { form: { // 接受父组件传递过来的值渲染表单 type: Object, default() { return { name: '', info: '', version: '', detail: '', status: '' } } } }, data() { return { rules: { name: [ { required: true, message: '请输入处理结果', trigger: 'blur' } ] } } }, methods: { submitForm() { this.$refs.form.validate(valid => { if (!valid) { return } this.$emit('submit', this.form) }) } } } </script> <style lang='scss' scoped> .deploy-form { position: relative; display: block; .btn-wrapper{ text-align: right; } } </style>
(5)views/release/list/index.vue
<template> <div class="release"> <div> <!--搜索--> <el-col :span="8"> <el-input v-model="params.search" placeholder="搜索" @keyup.enter.native="searchClick"> <el-button slot="append" icon="el-icon-search" @click="searchClick"/> </el-input> </el-col> </div> <!--表格--> <deploy-list :value="release" @edit="handleEdit" @delete="handleDelete"/> <!--模态窗--> <el-dialog :visible.sync="dialogVisibleForEdit" title="上线进度" width="50%"> <el-steps :active="active" finish-status="success" simple style="margin-top: 20px"> <el-step title="申请" /> <el-step title="审核" /> <el-step title="灰度" /> <el-step title="上线" /> </el-steps> <br> <deploy-form ref="releaseForm" :form="currentValue" @submit="handleSubmitEdit"/> </el-dialog> <!--分页--> <center> <el-pagination :page-size="pagesize" :total="totalNum" background layout="total, prev, pager, next, jumper" @current-change="handleCurrentChange"/> </center> </div> </template> <script> import { getDeployList, updateDeploy } from '@/api/release/release' import DeployList from './table' import DeployForm from './form' export default { name: 'Release', components: { DeployList, DeployForm }, data() { return { dialogVisibleForEdit: false, currentValue: {}, release: [], totalNum: 0, pagesize: 10, active: 1, params: { page: 1, search: '', ordering: '-deploy_time', status: 0 } } }, created() { this.fetchData() }, methods: { fetchData() { getDeployList(this.params).then( res => { this.release = res.results console.log(this.release) this.totalNum = res.count }) }, handleCurrentChange(val) { this.params.page = val this.fetchData() }, searchClick() { this.fetchData() }, /* 处理上线申请,弹出模态窗、提交数据、取消 */ handleEdit(value) { this.currentValue = { ...value } this.dialogVisibleForEdit = true this.params.status = this.currentValue.status.id this.active = this.params.status + 1 }, handleSubmitEdit(value) { const { id, ...params } = value console.log(params) const formdata = { 'status': this.params.status + 1, 'name': params.name, 'version': params.version } updateDeploy(id, formdata).then(res => { this.$message({ message: '更新成功', type: 'success' }) }) this.fetchData() this.dialogVisibleForEdit = false }, /* 取消 */ handleDelete(id) { console.log(id) const data = { 'status': 4 } updateDeploy(id, data).then(res => { this.$message({ message: '取消成功', type: 'success' }) this.fetchData() }, err => { console.log(err.message) }) } } } </script> <style lang='scss' scoped> .release { padding: 10px; } </style>
(6)views/release/list/table.vue
<template> <div class="deploy-list"> <el-table :data="value" border stripe style="width: 100%"> <el-table-column type="expand"> <template slot-scope="props"> <span><pre>版本描述:{{ props.row.info }}</pre></span> <span><pre>发布信息:{{ props.row.detail }}</pre></span> </template> </el-table-column> <el-table-column label="项目名称" prop="name"/> <el-table-column label="项目版本" prop="version"/> <el-table-column label="申请人" prop="applicant[0].name"/> <el-table-column label="审核人" prop="reviewer[0].name"/> <el-table-column label="状态" prop="status.name"/> <el-table-column :formatter="dateFormat" label="申请时间" prop="apply_time" /> <el-table-column label="操作"> <template slot-scope="scope"> <el-button size="mini" type="primary" @click="handleEdit(scope.row)">处理</el-button> <el-button size="mini" type="danger" @click="handleDelete(scope.row)">取消</el-button> </template> </el-table-column> </el-table> </div> </template> <script> import moment from 'moment' export default { name: 'DeployList', props: { value: { type: Array, default: function() { return [] } } }, methods: { /* 点击编辑按钮,将子组件的事件传递给父组件 */ handleEdit(value) { this.$emit('edit', value) }, /* 删除 */ handleDelete(value) { const id = value.id const name = value.name this.$confirm(`取消上线: ${name}, 是否继续?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.$emit('delete', id) }).catch(() => { this.$message({ type: 'info', message: '已取消删除' }) }) }, dateFormat: function(row, column) { const date = row[column.property] if (date === undefined) { return '' } return moment(date).format('YYYY-MM-DD HH:mm:ss') } } } </script> <style lang='scss'> </style>
效果如图:
报错--前端post请求报400错误:认证失败Uncaught (in promise) Error: Request failed with status code 400
一般是前后端交互的方法参数不一致,发生此错误需要仔细检查前端传递的参数数据的参数名、参数数目、参数类型是否与后端保持一致。
三.Jenkins api
jenkins部署: jdk安装: [root@VM_0_5_centos ~]#tar -zxvf jdk-8u261-linux-x64.tar.gz -C /usr/local/java/ [root@VM_0_5_centos ~]#vi /etc/profile export JAVA_HOME=/usr/local/java/jdk1.8.0_261 export JRE_HOME=${JAVA_HOME}/jre export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib export PATH=${JAVA_HOME}/bin:$PATH [root@VM_0_5_centos ~]#source /etc/profile [root@VM_0_5_centos ~]# java -version 安装配置: 各种报错参考此文档https://www.ddkiss.com/archives/167.html [root@VM_0_5_centos ~]# rpm -ivh jenkins-2.99-1.1.noarch.rpm [root@VM_0_5_centos ~]# vi /etc/sysconfig/jenkins JENKINS_USER="root"JENKINS_PORT="8088" 修改目录权限: [root@VM_0_5_centos ~]# chown -R root:root /var/lib/jenkins [root@VM_0_5_centos ~]# chown -R root:root /var/cache/jenkins [root@VM_0_5_centos ~]# chown -R root:root /var/log/jenkins [root@VM_0_5_centos ~]# vi /etc/init.d/jenkins 第一个java路径就是我自己加的! candidates=" /usr/local/java/jdk1.8.0_261/bin/java /etc/alternatives/java /usr/lib/jvm/java-1.8.0/bin/java /usr/lib/jvm/jre-1.8.0/bin/java /usr/lib/jvm/java-1.7.0/bin/java /usr/lib/jvm/jre-1.7.0/bin/java /usr/bin/java " [root@VM_0_5_centos ~]# systemctl start jenkins [root@VM_0_5_centos ~]# systemctl status jenkins 放通腾讯云服务器8088端口: (1)进入控制台页面-->安全组--->添加安全组

(2)点击刚创建的规则id--点关联实例---新增关联

这就就可直接在你windows机器访问腾讯云的jenkins服务了!!如下图:
jenkins要安装Git Parameter插件---支持参数化构建。如下图
2.jenkins api使用
(python36env) [vagrant@CentOS devops]$ pip install python-jenkins python36env) [vagrant@CentOS devops]$ ipython In [1]: import jenkins In [2]: JENKINS_URL = "http://120.53.122.**:8088" In [3]: JENKINS_USERNAME = 'root' In [4]: JENKINS_PASSWORD = '181818jn' In [5]: server = jenkins.Jenkins(JENKINS_URL, username = JENKINS_USERNAME, ...: password = JENKINS_PASSWORD) In [6]: server.get_job_info("51reboot")

In [9]:server.build_job("51reboot1", parameters={'tag': "version1.0"})
In [10]: server.build_job("51reboot1", parameters={'tag': "version1.1"})

In [13]: server.get_build_console_output("51reboot1", 2)
In [14]: server.get_job_info('51reboot1')['nextBuildNumber']
Out[14]: 3
(1)devops/settings.py:
JENKINS_URL = "http://120.53.122.**:8088" JENINS_TOKEN = "9b6bd371a9ba47511a0f04184e1076df" JENKINS_USERNAME = 'root' JENKINS_PASSWORD = '18181**'
(2)apps/utils/jenkis_api.py:
from jenkins import Jenkins from devops.settings import JENKINS_URL, JENKINS_USERNAME, JENKINS_PASSWORD class JenkinsApi: def __init__(self): self.url = JENKINS_URL self.username = JENKINS_USERNAME self.password = JENKINS_PASSWORD self.server = self.connect() def connect(self): """ 连接jenkins(实例化jenkins) :return: """ server = Jenkins(self.url, username=self.username, password=self.password) return server def get_next_build_number(self, name): """ 获取下一次构建号 :param name: 任务名称(项目名称) :return: "int" number """ return self.server.get_job_info(name)['nextBuildNumber'] def build_job(self, name, parameters=None): """ 构建任务 :param name: "str" 任务名称 :param parameters: "dict" 参数 :return: "int" queue number """ return self.server.build_job(name=name, parameters=parameters) def get_build_console_output(self, name, number): """ 获取终端输出结果 :param name: "str" 任务名称 :param number: "str" 构建号 :return: "str" 结果 """ return self.server.get_build_console_output(name, number)
(3)apps/release/views.py:
from utils.jenkins_api import JenkinsApi from .serializers import DeploySerializer from .models import Deploy from time import sleep User = get_user_model() class Pagination(PageNumberPagination): page_size = 10 page_size_query_param = 'page_size' page_query_param = "page" max_page_size = 100 class DeployViewset(viewsets.ModelViewSet): ....... def partial_update(self, request, *args, **kwargs): pk = kwargs.get("pk") data = request.data data['assign_to'] = request.user print(data) if int(data['status']) == 3: print("1111") jenkins = JenkinsApi() number = jenkins.get_next_build_number(data['name']) jenkins.build_job(data['name'], parameters={'tag': data['version']}) sleep(30) console_output = jenkins.get_build_console_output(data['name'], number) data['console_output'] = console_output Deploy.objects.filter(pk=pk).update(**data) return Response(status=status.HTTP_204_NO_CONTENT)
效果如图:
四.部署
参考:https://segmentfault.com/a/1190000004232816--如下说明
(1)devops/settings.py:
DEBUG = False ALLOWED_HOSTS = ["*"] STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, "static/")
(2)devops/urls.py:
from django.views.static import serve from restfuldemo.settings import STATIC_ROOT urlpatterns = [ url(r'^api/', include(router.urls)), url(r'^', include('resources.urls')), url(r'^api-auth/', include('rest_framework.urls')), url(r'^docs/', include_docs_urls("51reboot接口文档")), url('^projects/', include('projects.urls', namespace='projects')), url(r'^login/', obtain_jwt_token), url(r'^api-token-auth/', obtain_jwt_token), url(r'^static/(?P<path>.*)$', serve, {"document_root": STATIC_ROOT}), ]
(3)(python36env) [vagrant@CentOS devops]$ ls apps dashboard devops manage.py (python36env) [vagrant@CentOS devops]$ mkdir static (python36env) [vagrant@CentOS devops]$ ./manage.py collectstatic (python36env) [vagrant@CentOS devops]$ ls static/ admin rest_framework
这样就能正常访问了,如下图:
(4)gunicorn部署: (python36env) [vagrant@CentOS devops]$ pip install -U gunicorn (python36env) [vagrant@CentOS devops]$ gunicorn devops.wsgi:application -w 4 -b 0.0.0.0:8009 [2020-07-26 07:03:54 +0200] [4300] [INFO] Using worker: sync [2020-07-26 07:03:54 +0200] [4303] [INFO] Booting worker with pid: 4303....... (python36env) [vagrant@CentOS devops]$ gunicorn devops.wsgi:application -w 4 -b 0.0.0.0:8009 -D (python36env) [vagrant@CentOS devops]$ ps -ef|grep gun //(python36env) [vagrant@CentOS devops]$ killall gunicorn
报错:ERR_CONNECTION_REFUSED
(python36env) [vagrant@CentOS devops]$ curl 127.0.0.1:8009/api/ {"detail":"身份认证信息未提供。"}
(5)devops/conf/devops_nginx.conf
upstream django { # server unix:///path/to/your/mysite/mysite.sock; # for a file socket server 127.0.0.1:8004; # for a web port socket (we'll use this first) } # configuration of the server server { listen 80; server_name 127.0.0.1; # 指向django的static目录 location /static { alias /vagrant/devops/devops/static; } location / { uwsgi_pass 127.0.0.1:8009; include uwsgi_params; } error_log /tmp/devops8_error.log; access_log /tmp/devops8_access.log; }
(6)devops/conf/devops_uwsgi.ini
[uwsgi] # Django-related settings # the base directory (full path) chdir = /vagrant/devops/devops # Django's wsgi file module = devops.wsgi # the virtualenv (full path) # process-related settings # master master = true # maximum number of worker processes processes = 10 # the socket (use the full path to be safe socket = 0.0.0.0:8009 # ... with appropriate permissions - may be needed # chmod-socket = 664 # clear environment on exit vacuum = true virtualenv = /home/vagrant/python36env # pid file path pidfile = /tmp/devops.pid # background process and specify the log file daemonize = /tmp/devops.log # log file max size(KB) log-maxsize = 50000000
(python36env) [vagrant@CentOS devops]$pip install uwsgi (python36env) [vagrant@CentOS devops]$ ps -ef|grep gun vagrant 4579 1 1 08:05 ? 00:00:00 /home/vagrant/python36env/bin/python /home/vagrant/python36env/bin/gunicorn devops.wsgi:application -w 4 -b 0.0.0.0:8009 -D vagrant 4582 4579 17 08:05 ? 00:00:01 /home/vagrant/python36env/bin/python /home/vagr (python36env) [vagrant@CentOS devops]$ kill -HUP 4579 (python36env) [vagrant@CentOS devops]$ ps -ef|grep gun vagrant 4579 1 0 08:05 ? 00:00:00 /home/vagrant/python36env/bin/python /home/vagrant/python36env/bin/gunicorn devops.wsgi:application -w 4 -b 0.0.0.0:8009 -D (python36env) [vagrant@CentOS devops]$ cd conf (python36env) [vagrant@CentOS conf]$ ls devops_nginx.conf devops_uwsgi.ini (python36env) [vagrant@CentOS conf]$ uwsgi -i devops_uwsgi.ini (python36env) [vagrant@CentOS conf]$ ps -ef|grep 8009
1
2
3
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步