perf: add ANSIBLE_DOCKER_ENABLED configuration

This commit is contained in:
wangruidong
2026-06-16 16:34:40 +08:00
parent 9907f7e79c
commit 3028f3562f
6 changed files with 113 additions and 53 deletions

View File

@@ -589,6 +589,7 @@ class Config(dict):
'SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY': True,
'SECURITY_MFA_BY_EMAIL': False,
'SECURITY_COMMAND_EXECUTION': False,
'ANSIBLE_DOCKER_ENABLED': True,
'SECURITY_COMMAND_BLACKLIST': [
'reboot', 'shutdown', 'poweroff', 'halt', 'dd', 'half', 'top'
],

View File

@@ -43,6 +43,7 @@ SECURITY_MFA_BY_EMAIL = CONFIG.SECURITY_MFA_BY_EMAIL
SECURITY_MAX_IDLE_TIME = CONFIG.SECURITY_MAX_IDLE_TIME # Unit: minute
SECURITY_MAX_SESSION_TIME = CONFIG.SECURITY_MAX_SESSION_TIME # Unit: hour
SECURITY_COMMAND_EXECUTION = CONFIG.SECURITY_COMMAND_EXECUTION
ANSIBLE_DOCKER_ENABLED = CONFIG.ANSIBLE_DOCKER_ENABLED
SECURITY_COMMAND_BLACKLIST = CONFIG.SECURITY_COMMAND_BLACKLIST
SECURITY_PASSWORD_EXPIRATION_TIME_ADMIN = CONFIG.SECURITY_PASSWORD_EXPIRATION_TIME_ADMIN # Unit: day
SECURITY_PASSWORD_EXPIRATION_TIME = CONFIG.SECURITY_PASSWORD_EXPIRATION_TIME # Unit: day

View File

@@ -0,0 +1,76 @@
import os
import shutil
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from common.utils.safe import safe_run_cmd
from .exception import AnsibleDockerImageNotFound
ANSIBLE_EE_IMAGE = 'jumpserver/ansible-executor:latest'
ANSIBLE_EE_PYTHON_INTERPRETER = '/usr/bin/python3.11'
__all__ = [
'ANSIBLE_EE_IMAGE',
'ANSIBLE_EE_PYTHON_INTERPRETER',
'use_ansible_docker_isolation',
'docker_extravars',
'docker_isolation_kwargs',
'prepare_isolated_ansible_cfg',
'stage_inventory_for_docker',
'ensure_ansible_docker_image',
]
def use_ansible_docker_isolation():
return settings.ANSIBLE_DOCKER_ENABLED
def docker_extravars(extra_vars):
extravars = dict(extra_vars or {})
if use_ansible_docker_isolation():
extravars.setdefault('local_python_interpreter', ANSIBLE_EE_PYTHON_INTERPRETER)
return extravars
def docker_isolation_kwargs():
return {
'process_isolation': True,
'process_isolation_executable': 'docker',
'container_image': ANSIBLE_EE_IMAGE,
'container_options': ['--network=jms_net'],
}
def prepare_isolated_ansible_cfg(project_dir):
if not use_ansible_docker_isolation():
return
src = os.path.join(settings.APPS_DIR, 'libs', 'ansible', 'ansible.cfg')
dst = os.path.join(project_dir, 'ansible.cfg')
shutil.copyfile(src, dst)
def stage_inventory_for_docker(project_dir, inventory_path):
if not use_ansible_docker_isolation():
return inventory_path
standard_dir = os.path.join(project_dir, 'inventory')
standard_path = os.path.join(standard_dir, 'hosts')
if os.path.realpath(inventory_path) == os.path.realpath(standard_path):
return standard_path
os.makedirs(standard_dir, mode=0o700, exist_ok=True)
shutil.copy2(inventory_path, standard_path)
return standard_path
def ensure_ansible_docker_image():
if not use_ansible_docker_isolation():
return
result = safe_run_cmd(['docker', 'image', 'inspect', ANSIBLE_EE_IMAGE])
if not result or result.returncode != 0:
raise AnsibleDockerImageNotFound(
_('Ansible Docker image "%(image)s" not found. '
'You can disable this option in System Settings - Feature Settings - Job Center - '
'Ansible Docker isolation to run locally. '
'Please run: docker pull %(image)s')
% {'image': ANSIBLE_EE_IMAGE}
)

View File

@@ -1,5 +1,9 @@
__all__ = ['CommandInBlackListException']
__all__ = ['CommandInBlackListException', 'AnsibleDockerImageNotFound']
class CommandInBlackListException(Exception):
pass
class AnsibleDockerImageNotFound(Exception):
pass

View File

@@ -6,65 +6,28 @@ from django.conf import settings
from django.utils._os import safe_join
from common.utils import is_macos
from ..utils import get_ansible_log_verbosity
from .callback import DefaultCallback
from .docker import (
docker_extravars,
docker_isolation_kwargs,
ensure_ansible_docker_image,
prepare_isolated_ansible_cfg,
stage_inventory_for_docker,
use_ansible_docker_isolation,
)
from .exception import CommandInBlackListException
from .interface import interface
from ..utils import get_ansible_log_verbosity
__all__ = ['AdHocRunner', 'PlaybookRunner', 'SuperPlaybookRunner', 'UploadFileRunner']
ANSIBLE_EE_IMAGE = 'jumpserver/ansible-executor:latest'
ANSIBLE_EE_PYTHON_INTERPRETER = '/usr/bin/python3.11'
def docker_extravars(extra_vars):
extravars = dict(extra_vars or {})
if use_ansible_docker_isolation():
extravars.setdefault('local_python_interpreter', ANSIBLE_EE_PYTHON_INTERPRETER)
return extravars
def use_ansible_docker_isolation():
return True
def docker_isolation_kwargs():
return {
'process_isolation': True,
'process_isolation_executable': 'docker',
'container_image': ANSIBLE_EE_IMAGE,
'container_options': ['--network=jms_net'],
}
def prepare_isolated_ansible_cfg(project_dir):
"""Copy ansible.cfg into job dir so the EE container picks up SSH settings."""
if not use_ansible_docker_isolation():
return
src = os.path.join(settings.APPS_DIR, 'libs', 'ansible', 'ansible.cfg')
dst = os.path.join(project_dir, 'ansible.cfg')
shutil.copyfile(src, dst)
def stage_inventory_for_docker(project_dir, inventory_path):
"""Stage custom inventory into private_data_dir/inventory/hosts for Docker EE."""
if not use_ansible_docker_isolation():
return inventory_path
standard_dir = os.path.join(project_dir, 'inventory')
standard_path = os.path.join(standard_dir, 'hosts')
if os.path.realpath(inventory_path) == os.path.realpath(standard_path):
return standard_path
os.makedirs(standard_dir, mode=0o700, exist_ok=True)
shutil.copy2(inventory_path, standard_path)
return standard_path
class AdHocRunner:
cmd_modules_choices = ('shell', 'raw', 'command', 'script', 'win_shell')
need_local_connection_modules_choices = ("mysql", "postgresql", "sqlserver", "huawei")
def __init__(self, inventory, job_module, module, module_args='', pattern='*', project_dir='/tmp/',
extra_vars=None,
def __init__(self, inventory, job_module, module, module_args='', pattern='*', project_dir='/tmp/', extra_vars=None,
dry_run=False, timeout=-1):
if extra_vars is None:
extra_vars = {}
@@ -100,7 +63,7 @@ class AdHocRunner:
verbosity = get_ansible_log_verbosity(verbosity)
if not os.path.exists(self.project_dir):
os.mkdir(self.project_dir, 0o755)
os.makedirs(self.project_dir, 0o755, exist_ok=True)
private_env = safe_join(self.project_dir, 'env')
if os.path.exists(private_env):
shutil.rmtree(private_env)
@@ -123,7 +86,7 @@ class AdHocRunner:
}
if use_ansible_docker_isolation():
run_kwargs.update(docker_isolation_kwargs())
ensure_ansible_docker_image()
interface.run(**run_kwargs)
return self.cb
@@ -172,6 +135,7 @@ class PlaybookRunner:
kwargs = dict(kwargs)
if use_ansible_docker_isolation():
kwargs.update(docker_isolation_kwargs())
ensure_ansible_docker_image()
elif self.isolate and not is_macos():
kwargs['process_isolation'] = True
kwargs['process_isolation_executable'] = 'bwrap'
@@ -223,7 +187,7 @@ class UploadFileRunner:
def run(self, verbosity=0, **kwargs):
if not os.path.exists(self.project_dir):
os.makedirs(self.project_dir, mode=0o755)
os.makedirs(self.project_dir, mode=0o755, exist_ok=True)
prepare_isolated_ansible_cfg(self.project_dir)
src_path = self.stage_upload_files()
@@ -243,7 +207,7 @@ class UploadFileRunner:
}
if use_ansible_docker_isolation():
run_kwargs.update(docker_isolation_kwargs())
ensure_ansible_docker_image()
interface.run(**run_kwargs)
try:
shutil.rmtree(self.share_src_dir)

View File

@@ -6,6 +6,7 @@ from rest_framework import serializers
from common.serializers.fields import EncryptedField
from common.utils import date_expired_default
from ops.ansible.docker import ANSIBLE_EE_IMAGE
__all__ = [
'AnnouncementSettingSerializer', 'OpsSettingSerializer', 'VaultSettingSerializer',
@@ -17,6 +18,14 @@ from settings.const import (
ChatAITypeChoices, GPTModelChoices, DeepSeekModelChoices, ChatAIMethodChoices
)
ANSIBLE_DOCKER_HELP_TEXT = _(
'Run Ansible jobs in Docker execution environment (%(image)s). '
'You can disable this option in System Settings - Feature Settings - Job Center - '
'Ansible Docker isolation to run locally. '
'If the image is missing, pull it on the ansible worker: '
'docker pull %(image)s'
) % {'image': ANSIBLE_EE_IMAGE}
class AnnouncementSerializer(serializers.Serializer):
ID = serializers.CharField(required=False, allow_blank=True, allow_null=True)
@@ -204,6 +213,11 @@ class OpsSettingSerializer(serializers.Serializer):
required=False, label=_('Adhoc command'),
help_text=_('Allow users to execute batch commands in the Workbench - Job Center - Adhoc')
)
ANSIBLE_DOCKER_ENABLED = serializers.BooleanField(
required=False,
label=_('Ansible Docker isolation'),
help_text=ANSIBLE_DOCKER_HELP_TEXT,
)
SECURITY_COMMAND_BLACKLIST = serializers.ListField(
child=serializers.CharField(max_length=1024),
label=_('Command blacklist'),