From d18f64954495982b4c630581d7720c25e579ecd6 Mon Sep 17 00:00:00 2001 From: xcwang <1366993017@qq.com> Date: Tue, 29 Nov 2022 21:33:26 +0800 Subject: [PATCH] =?UTF-8?q?feature:=20=20Proxy=20Nginx=20=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=20v6=20=E6=94=AF=E6=8C=81=20(closed=20#1289)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/backend/agent/manager.py | 13 +-- .../collections/agent_new/components.py | 7 ++ .../collections/agent_new/start_nginx.py | 47 ++++++++++ .../collections/common/script_content.py | 89 +++++++++++++++++++ apps/backend/subscription/errors.py | 8 ++ .../collections/agent_new/test_start_nginx.py | 40 +++++++++ dev_log/2.2.32/xcwang_202211292133.yaml | 3 + script_tools/start_nginx.sh.tpl | 79 ---------------- 8 files changed, 195 insertions(+), 91 deletions(-) create mode 100644 apps/backend/components/collections/agent_new/start_nginx.py create mode 100644 apps/backend/tests/components/collections/agent_new/test_start_nginx.py create mode 100644 dev_log/2.2.32/xcwang_202211292133.yaml delete mode 100644 script_tools/start_nginx.sh.tpl diff --git a/apps/backend/agent/manager.py b/apps/backend/agent/manager.py index 9e1c822be..a50c90c5c 100644 --- a/apps/backend/agent/manager.py +++ b/apps/backend/agent/manager.py @@ -8,7 +8,6 @@ an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ -import os from typing import Any, Dict, List from django.conf import settings @@ -171,17 +170,7 @@ def push_files_to_proxy(cls, file: Dict[str, Any]): @classmethod def start_nginx(cls): """启动 NGINX 服务""" - act = AgentServiceActivity(component_code=components.AgentExecuteScriptComponent.code, name=_("启动 NGINX 服务")) - with open(os.path.join(settings.BK_SCRIPTS_PATH, "start_nginx.sh.tpl"), encoding="utf-8") as fh: - script = fh.read() - # 脚本模板中存在 {print $2} 等和 format 关键字冲突的片段 - # 此处的字符串渲染采用 % 的方式 - script_content = script % { - "nginx_path": settings.DOWNLOAD_PATH, - "bk_nodeman_nginx_download_port": settings.BK_NODEMAN_NGINX_DOWNLOAD_PORT, - "bk_nodeman_nginx_proxy_pass_port": settings.BK_NODEMAN_NGINX_PROXY_PASS_PORT, - } - act.component.inputs.script_content = Var(type=Var.PLAIN, value=script_content) + act = AgentServiceActivity(component_code=components.StartNginxComponent.code, name=_("启动 NGINX 服务")) act.component.inputs.script_param = Var(type=Var.PLAIN, value="") return act diff --git a/apps/backend/components/collections/agent_new/components.py b/apps/backend/components/collections/agent_new/components.py index 8ce82fa4f..af879aabe 100644 --- a/apps/backend/components/collections/agent_new/components.py +++ b/apps/backend/components/collections/agent_new/components.py @@ -31,6 +31,7 @@ from .render_and_push_gse_config import RenderAndPushGseConfigService from .restart import RestartService from .run_upgrade_command import RunUpgradeCommandService +from .start_nginx import StartNginxService from .unbind_host_agent import UnBindHostAgentService from .update_install_info import UpdateInstallInfoService from .update_process_status import UpdateProcessStatusService @@ -179,3 +180,9 @@ class AddOrUpdateHostsComponent(Component): name = _("新增或更新主机信息") code = "add_or_update_hosts" bound_service = AddOrUpdateHostsService + + +class StartNginxComponent(Component): + name = _("启动Nginx") + code = "start_nginx" + bound_service = StartNginxService diff --git a/apps/backend/components/collections/agent_new/start_nginx.py b/apps/backend/components/collections/agent_new/start_nginx.py new file mode 100644 index 000000000..95a9a22c9 --- /dev/null +++ b/apps/backend/components/collections/agent_new/start_nginx.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +""" +TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-节点管理(BlueKing-BK-NODEMAN) available. +Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at https://opensource.org/licenses/MIT +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +from typing import Dict, Union + +from django.conf import settings +from jinja2 import Template + +from apps.backend.components.collections.agent_new.base import ( + AgentCommonData, + AgentExecuteScriptService, +) +from apps.backend.components.collections.common.script_content import ( + START_NGINX_TEMPLATE, +) +from apps.backend.subscription.errors import ScriptRenderFailed +from apps.node_man import models +from apps.utils import basic + + +class StartNginxService(AgentExecuteScriptService): + @property + def script_name(self): + return "start_nginx" + + def get_script_content(self, data, common_data: AgentCommonData, host: models.Host) -> str: + template = Template(START_NGINX_TEMPLATE) + script_variable_map: Dict[str, Union[str, bool]] = { + "nginx_path": settings.DOWNLOAD_PATH, + "bk_nodeman_nginx_download_port": settings.BK_NODEMAN_NGINX_DOWNLOAD_PORT, + "bk_nodeman_nginx_proxy_pass_port": settings.BK_NODEMAN_NGINX_PROXY_PASS_PORT, + "is_v6_host": basic.is_v6(host.inner_ipv6), + "inner_ipv6": host.inner_ipv6, + } + try: + content: str = template.render(script_variable_map) + except Exception as e: + raise ScriptRenderFailed({"name": self.script_name, "msg": e}) + + return content diff --git a/apps/backend/components/collections/common/script_content.py b/apps/backend/components/collections/common/script_content.py index c4e1bc5d0..9ce8e6ac2 100644 --- a/apps/backend/components/collections/common/script_content.py +++ b/apps/backend/components/collections/common/script_content.py @@ -45,3 +45,92 @@ fi done """ + +START_NGINX_TEMPLATE = """ +/opt/nginx-portable/nginx-portable stop || :; +rm -rf /opt/nginx-portable/; +rm -rf /opt/py36/; +tar xvf {{ nginx_path }}/py36.tgz -C /opt; +tar xvf {{ nginx_path }}/nginx-portable.tgz -C /opt; +chmod -R 755 /data +user=root +group=root +#create group if not exists +egrep "^$group" /etc/group >& /dev/null +if [ $? -ne 0 ] +then + groupadd $group +fi + +#create user if not exists +egrep "^$user" /etc/passwd >& /dev/null +if [ $? -ne 0 ] +then + useradd -g $group $user +fi +DNS_LIST=$(awk 'BEGIN{ORS=" "} $1=="nameserver" {print $2}' /etc/resolv.conf) +if ! grep -q "nameserver.*127.0.0.1" /etc/resolv.conf; then + DNS_LIST+=(127.0.0.1) +fi +echo -e " +user $user; +events { + worker_connections 65535; +} +http { + include mime.types; + default_type application/octet-stream; + sendfile on; + server { + listen {{ bk_nodeman_nginx_download_port }}; + server_name localhost; + root {{ nginx_path }} + + location / { + index index.html; + } + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + } + server { + listen {{ bk_nodeman_nginx_proxy_pass_port }}; + {% if is_v6_host %} + listen [::]:{{ bk_nodeman_nginx_proxy_pass_port }}; + {% endif %} + server_name localhost; + {% if is_v6_host %} + resolver [{{ inner_ipv6 }}] ${DNS_LIST[@]}; + {% else %} + resolver ${DNS_LIST[@]}; + {% endif %} + proxy_connect; + proxy_connect_allow 443 563; + location / { + proxy_pass http://$http_host$request_uri; + } + } +}" > /opt/nginx-portable/conf/nginx.conf; +/opt/nginx-portable/nginx-portable start; +sleep 5 +is_port_listen_by_pid () { + local pid regex ret + pid=$1 + shift 1 + ret=0 + + for i in {0..10}; do + sleep 1 + for port in "$@"; do + stat -L -c %%i /proc/"$pid"/fd/* 2>/dev/null | grep -qwFf - \ + <( awk -v p="$port" 'BEGIN{ check=sprintf(":%%04X0A$", p)} $2$4 ~ check {print $10}' \ + /proc/net/tcp*) || ((ret+=1)) + done + done + return "$ret" +} +pid=$(cat /opt/nginx-portable/logs/nginx.pid); +is_port_listen_by_pid "$pid" {{ bk_nodeman_nginx_download_port }} {{ bk_nodeman_nginx_proxy_pass_port}} +exit $? +""" diff --git a/apps/backend/subscription/errors.py b/apps/backend/subscription/errors.py index 617b18a25..197ea6402 100644 --- a/apps/backend/subscription/errors.py +++ b/apps/backend/subscription/errors.py @@ -161,3 +161,11 @@ class PluginScriptValidationError(AppBaseException): ERROR_CODE = 18 MESSAGE = _("插件操作脚本校验错误") MESSAGE_TPL = _("{msg}") + + +class ScriptRenderFailed(AppBaseException): + """实例有执行中任务""" + + ERROR_CODE = 19 + MESSAGE = _("作业下发脚本渲染失败") + MESSAGE_TPL = _("作业下发脚本[{name}]渲染失败,原因:{msg}") diff --git a/apps/backend/tests/components/collections/agent_new/test_start_nginx.py b/apps/backend/tests/components/collections/agent_new/test_start_nginx.py new file mode 100644 index 000000000..74595c552 --- /dev/null +++ b/apps/backend/tests/components/collections/agent_new/test_start_nginx.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +""" +TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-节点管理(BlueKing-BK-NODEMAN) available. +Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at https://opensource.org/licenses/MIT +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +# -*- coding: utf-8 -*- + + +from apps.backend.components.collections.agent_new import components +from apps.node_man import constants, models + +from . import base + + +class StartIpv4ProxyNginxTestCase(base.JobBaseTestCase): + @classmethod + def get_default_case_name(cls) -> str: + return "启动 Ipv4 Proxy Nginx 成功" + + def component_cls(self): + return components.StartNginxComponent + + +class StartIpv6ProxyNginxTestCase(base.JobBaseTestCase): + @classmethod + def get_default_case_name(cls) -> str: + return "启动 Ipv6 Proxy Nginx 成功" + + @classmethod + def setUpTestData(cls): + super().setUpTestData() + models.Host.objects.all().update(node_type=constants.NodeType.PROXY, inner_ipv6="2001:db8:85a3::8a2e:370:7334") + + def component_cls(self): + return components.StartNginxComponent diff --git a/dev_log/2.2.32/xcwang_202211292133.yaml b/dev_log/2.2.32/xcwang_202211292133.yaml new file mode 100644 index 000000000..dedc5f6fc --- /dev/null +++ b/dev_log/2.2.32/xcwang_202211292133.yaml @@ -0,0 +1,3 @@ +--- +feature: + - "Proxy Nginx 配置 v6 支持 (closed #1289)" diff --git a/script_tools/start_nginx.sh.tpl b/script_tools/start_nginx.sh.tpl deleted file mode 100644 index dd20c7016..000000000 --- a/script_tools/start_nginx.sh.tpl +++ /dev/null @@ -1,79 +0,0 @@ -/opt/nginx-portable/nginx-portable stop || :; -rm -rf /opt/nginx-portable/; -rm -rf /opt/py36/; -tar xvf %(nginx_path)s/py36.tgz -C /opt; -tar xvf %(nginx_path)s/nginx-portable.tgz -C /opt; -chmod -R 755 /data -user=root -group=root -#create group if not exists -egrep "^$group" /etc/group >& /dev/null -if [ $? -ne 0 ] -then - groupadd $group -fi - -#create user if not exists -egrep "^$user" /etc/passwd >& /dev/null -if [ $? -ne 0 ] -then - useradd -g $group $user -fi -DNS_LIST=$(awk 'BEGIN{ORS=" "} $1=="nameserver" {print $2}' /etc/resolv.conf) -if ! grep -q "nameserver.*127.0.0.1" /etc/resolv.conf; then - DNS_LIST+=(127.0.0.1) -fi -echo -e " -user $user; -events { - worker_connections 65535; -} -http { - include mime.types; - default_type application/octet-stream; - sendfile on; - server { - listen %(bk_nodeman_nginx_download_port)s; - server_name localhost; - root %(nginx_path)s; - - location / { - index index.html; - } - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root html; - } - } - server { - listen %(bk_nodeman_nginx_proxy_pass_port)s; - server_name localhost; - resolver ${DNS_LIST[@]}; - proxy_connect; - proxy_connect_allow 443 563; - location / { - proxy_pass http://\$http_host\$request_uri; - } - } -}" > /opt/nginx-portable/conf/nginx.conf; -/opt/nginx-portable/nginx-portable start; -sleep 5 -is_port_listen_by_pid () { - local pid regex ret - pid=$1 - shift 1 - ret=0 - - for i in {0..10}; do - sleep 1 - for port in "$@"; do - stat -L -c %%i /proc/"$pid"/fd/* 2>/dev/null | grep -qwFf - \ - <( awk -v p="$port" 'BEGIN{ check=sprintf(":%%04X0A$", p)} $2$4 ~ check {print $10}' \ - /proc/net/tcp) || ((ret+=1)) - done - done - return "$ret" -} -pid=$(cat /opt/nginx-portable/logs/nginx.pid); -is_port_listen_by_pid "$pid" %(bk_nodeman_nginx_download_port)s %(bk_nodeman_nginx_proxy_pass_port)s -exit $? \ No newline at end of file