Kubernetes 部署 JumpServer 与钉钉告警集成指南

本文详细介绍了在 Kubernetes 集群中部署 JumpServer 堡垒机的完整流程,包含 Ingress 配置、Helm 安装/更新/卸载操作指南,以及如何通过修改 Core 组件镜像实现钉钉告警功能的集成。内容涵盖核心组件的 Dockerfile 定制、values.yaml 配置解析和 K8s 部署调整。

这篇文章已发布 489 天,部分内容可能已过时。如有疑问,可在评论区留言。

ingress

需要搭建 ingress(详见13-K8s ingress-nginx 服务证书)并开启 TCP 转发(详见23-K8s ingress TCP 转发)。

jumpserver

  • “安装”

    1
    
    helm install jms-k8s ./jumpserver-v4.6.0 -n jumpserver --create-namespace -f values.yaml
    
  • “更新”

    1
    
    helm upgrade jms-k8s ./jumpserver-v4.6.0 -n jumpserver --create-namespace -f values.yaml
    
  • “卸载”

    1
    
    helm -n jumpserver delete jms-k8s
    

core 组件镜像

修改 core 组件,支持钉钉告警

1
2
3
4
docker pull docker.1ms.run/jumpserver/core:v4.6.0-ce

cd core
docker build -t jumpserver/core:v4.6.1-ce .
  • “Dockerfile”

    1
    2
    3
    
    FROM docker.1ms.run/jumpserver/core:v4.6.0-ce
    
    COPY ./notifications.py /opt/jumpserver/apps/notifications/notifications.py
    
  • “notifications.py”

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    
    import textwrap
    import time
    import traceback
    from itertools import chain
    
    from celery import shared_task
    from django.utils.translation import gettext_lazy as _
    from html2text import HTML2Text
    
    from common.utils import lazyproperty
    from common.utils.timezone import local_now
    from notifications.backends import BACKEND
    from settings.utils import get_login_title
    from terminal.const import RiskLevelChoices
    from users.models import User
    from .models import SystemMsgSubscription, UserMsgSubscription
    
    __all__ = ('SystemMessage', 'UserMessage', 'system_msgs', 'Message')
    
    system_msgs = []
    user_msgs = []
    
    
    class MessageType(type):
    	def __new__(cls, name, bases, attrs: dict):
    		clz = type.__new__(cls, name, bases, attrs)
    
    		if 'message_type_label' in attrs \
    				and 'category' in attrs \
    				and 'category_label' in attrs:
    			message_type = clz.get_message_type()
    
    			msg = {
    				'message_type': message_type,
    				'message_type_label': attrs['message_type_label'],
    				'category': attrs['category'],
    				'category_label': attrs['category_label'],
    			}
    			if issubclass(clz, SystemMessage):
    				system_msgs.append(msg)
    			elif issubclass(clz, UserMessage):
    				user_msgs.append(msg)
    
    		return clz
    
    
    @shared_task(
    	verbose_name=_('Publish the station message'),
    	description=_(
    		"""This task needs to be executed for sending internal messages for system alerts, 
    		work orders, and other notifications"""
    	)
    )
    def publish_task(receive_user_ids, backends_msg_mapper):
    	Message.send_msg(receive_user_ids, backends_msg_mapper)
    
    
    def send_dingtalk_task(message):
    	# 发送到自定义的 Webhook
    	import hmac, hashlib, base64, urllib.parse
    	timestamp = str(round(time.time() * 1000))
    	# JumpServer
    	access_token = '<access token>'
    	secret = '<secret>'
    
    	# smartvision
    	secret_enc = secret.encode('utf-8')
    	string_to_sign = '{}\n{}'.format(timestamp, secret)
    	string_to_sign_enc = string_to_sign.encode('utf-8')
    	hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
    	sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
    	form_data = {
    		"msgtype": "markdown",
    		"markdown": {
    			"title": "JumpServer 监控报警",
    			"text": message
    		},
    		"at": {
    			"atMobiles": [
    				"<mobile>"
    			],
    			"isAtAll": False
    		}
    	}
    	import requests
    	try:
    		res = requests.post(
    			url=f'https://oapi.dingtalk.com/robot/send?access_token={access_token}&timestamp={timestamp}&sign={sign}',
    			json=form_data)
    	except Exception as e:
    		with open('/opt/error.log', 'a+') as f:
    			f.write(str(e))
    
    
    class Message(metaclass=MessageType):
    	"""
    	这里封装了什么?
    		封装不同消息的模板,提供统一的发送消息的接口
    		- publish 该方法的实现与消息订阅的表结构有关
    		- send_msg
    	"""
    	message_type_label: str
    	category: str
    	category_label: str
    	text_msg_ignore_links = True
    	command = None
    
    	@classmethod
    	def get_message_type(cls):
    		return cls.__name__
    
    	def publish_async(self):
    		self.publish(is_async=True)
    
    	@classmethod
    	def gen_test_msg(cls):
    		raise NotImplementedError
    
    	def publish(self, is_async=False):
    		raise NotImplementedError
    
    	def get_backend_msg_mapper(self, backends):
    		backends = set(backends)
    		backends.add(BACKEND.SITE_MSG)  # 站内信必须发
    		backends_msg_mapper = {}
    		for backend in backends:
    			backend = BACKEND(backend)
    			if not backend.is_enable:
    				continue
    			get_msg_method = getattr(self, f'get_{backend}_msg', self.get_common_msg)
    			msg = get_msg_method()
    			backends_msg_mapper[backend] = msg
    		return backends_msg_mapper
    
    	@staticmethod
    	def send_msg(receive_user_ids, backends_msg_mapper):
    		for backend, msg in backends_msg_mapper.items():
    			try:
    				backend = BACKEND(backend)
    				client = backend.client()
    				users = User.objects.filter(id__in=receive_user_ids).all()
    				client.send_msg(users, **msg)
    			except NotImplementedError:
    				continue
    			except:
    				traceback.print_exc()
    
    	@classmethod
    	def send_test_msg(cls, ding=True, wecom=False):
    		msg = cls.gen_test_msg()
    		if not msg:
    			return
    
    		from users.models import User
    		users = User.objects.filter(username='admin')
    		backends = []
    		if ding:
    			backends.append(BACKEND.DINGTALK)
    		if wecom:
    			backends.append(BACKEND.WECOM)
    		msg.send_msg(users, backends)
    
    	@staticmethod
    	def get_common_msg() -> dict:
    		return {'subject': '', 'message': ''}
    
    	def get_html_msg(self) -> dict:
    		return self.get_common_msg()
    
    	@staticmethod
    	def html_to_markdown(html_msg):
    		h = HTML2Text()
    		h.body_width = 0
    		content = html_msg['message']
    		html_msg['message'] = h.handle(content)
    		return html_msg
    
    	def get_markdown_msg(self) -> dict:
    		return self.html_to_markdown(self.get_html_msg())
    
    	def get_text_msg(self) -> dict:
    		h = HTML2Text()
    		h.body_width = 90
    		msg = self.get_html_msg()
    		content = msg['message']
    		h.ignore_links = self.text_msg_ignore_links
    		msg['message'] = h.handle(content)
    		return msg
    
    	@lazyproperty
    	def common_msg(self) -> dict:
    		return self.get_common_msg()
    
    	@lazyproperty
    	def text_msg(self) -> dict:
    		msg = self.get_text_msg()
    		return msg
    
    	@lazyproperty
    	def markdown_msg(self):
    		return self.get_markdown_msg()
    
    	@lazyproperty
    	def get_jumpserver_dingtalk_msg(self) -> str:
    		date_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    		msg = f"""## <font color="#FF0000">[JumpServer 监控告警](https://jumpserver.example.com/)</font>🔥
    ### <font color="#FF0000">告警状态</font>:{RiskLevelChoices.get_label(self.command['risk_level'])}
    ### <font color="#FF0000">告警主机</font>:{self.command['asset']}
    ### <font color="#FF0000">目标用户</font>:{self.command['user']}
    ### <font color="#FF0000">输入命令</font>:`{self.command['input']}`
    ### <font color="#FF0000">告警详情</font>:用户 {self.command['user']}{self.command['asset']} 上执行了危险命令 `{self.command['input']}`,请及时处理!
    ### <font color="#FF0000">触发时间</font>:{date_str}"""
    		return msg
    
    	@lazyproperty
    	def html_msg(self) -> dict:
    		msg = self.get_html_msg()
    		return msg
    
    	@lazyproperty
    	def html_msg_with_sign(self):
    		msg = self.get_html_msg()
    		msg['message'] = textwrap.dedent("""
    		{}
    		<small>
    		<br />
    		<br />
    		{}
    		</small>
    		""").format(msg['message'], self.signature)
    		return msg
    
    	@lazyproperty
    	def text_msg_with_sign(self):
    		msg = self.get_text_msg()
    		msg['message'] = textwrap.dedent("""
    		{}
    		{}
    		""").format(msg['message'], self.signature)
    		return msg
    
    	@lazyproperty
    	def signature(self):
    		return get_login_title()
    
    	# --------------------------------------------------------------
    	# 支持不同发送消息的方式定义自己的消息内容,比如有些支持 html 标签
    	def get_dingtalk_msg(self) -> dict:
    		# 钉钉相同的消息一天只能发一次,所以给所有消息添加基于时间的序号,使他们不相同
    		message = self.markdown_msg['message']
    		time = local_now().strftime('%Y-%m-%d %H:%M:%S')
    		suffix = '\n{}: {}'.format(_('Time'), time)
    
    		return {
    			'subject': self.markdown_msg['subject'],
    			'message': message + suffix
    		}
    
    	def get_wecom_msg(self) -> dict:
    		return self.markdown_msg
    
    	def get_feishu_msg(self) -> dict:
    		return self.markdown_msg
    
    	def get_lark_msg(self) -> dict:
    		return self.markdown_msg
    
    	def get_email_msg(self) -> dict:
    		return self.html_msg_with_sign
    
    	def get_site_msg_msg(self) -> dict:
    		return self.html_msg
    
    	def get_slack_msg(self) -> dict:
    		return self.markdown_msg
    
    	def get_sms_msg(self) -> dict:
    		return self.text_msg_with_sign
    
    	@classmethod
    	def get_all_sub_messages(cls):
    		def get_subclasses(cls):
    			"""returns all subclasses of argument, cls"""
    			if issubclass(cls, type):
    				subclasses = cls.__subclasses__(cls)
    			else:
    				subclasses = cls.__subclasses__()
    			for subclass in subclasses:
    				subclasses.extend(get_subclasses(subclass))
    			return subclasses
    
    		messages_cls = get_subclasses(cls)
    		return messages_cls
    
    	@classmethod
    	def test_all_messages(cls, ding=True, wecom=False):
    		messages_cls = cls.get_all_sub_messages()
    
    		for _cls in messages_cls:
    			try:
    				_cls.send_test_msg(ding=ding, wecom=wecom)
    			except NotImplementedError:
    				continue
    
    
    class SystemMessage(Message):
    	def publish(self, is_async=False):
    		subscription = SystemMsgSubscription.objects.get(
    			message_type=self.get_message_type()
    		)
    
    		# 只发送当前有效后端
    		receive_backends = subscription.receive_backends
    		receive_backends = BACKEND.filter_enable_backends(receive_backends)
    
    		users = [
    			*subscription.users.all(),
    			*chain(*[g.users.all() for g in subscription.groups.all()])
    		]
    
    		receive_user_ids = [u.id for u in users]
    		backends_msg_mapper = self.get_backend_msg_mapper(receive_backends)
    		if is_async:
    			send_dingtalk_task(self.get_jumpserver_dingtalk_msg)
    			# publish_task.delay(receive_user_ids, backends_msg_mapper)
    		else:
    			self.send_msg(receive_user_ids, backends_msg_mapper)
    
    	@classmethod
    	def post_insert_to_db(cls, subscription: SystemMsgSubscription):
    		pass
    
    	@classmethod
    	def gen_test_msg(cls):
    		raise NotImplementedError
    
    
    class UserMessage(Message):
    	user: User
    
    	def __init__(self, user):
    		self.user = user
    
    	def publish(self, is_async=False):
    		"""
    		发送消息到每个用户配置的接收方式上
    		"""
    		sub = UserMsgSubscription.objects.get(user=self.user)
    		backends_msg_mapper = self.get_backend_msg_mapper(sub.receive_backends)
    		receive_user_ids = [self.user.id]
    		if is_async:
    			send_dingtalk_task(self.get_jumpserver_dingtalk_msg)
    			# publish_task.delay(receive_user_ids, backends_msg_mapper)
    		else:
    			self.send_msg(receive_user_ids, backends_msg_mapper)
    
    	@classmethod
    	def get_test_user(cls):
    		from users.models import User
    		return User.objects.all().first()
    
    	@classmethod
    	def gen_test_msg(cls):
    		raise NotImplementedError
    

values.yaml

```yaml
# Default values for jumpserver.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

nameOverride: ""
fullnameOverride: ""

## @param global.imageRegistry Global Docker image registry
## @param global.imagePullSecrets Global Docker registry secret names as an array
## @param global.storageClass Global StorageClass for Persistent Volume(s)
## @param global.redis.password Global Redis™ password (overrides `externalRedis.password`)
##
global:
imageRegistry: docker.1ms.run
imageOwner: jumpserver
## E.g.
#  imagePullSecrets:
#  - myRegistryKeySecretName
##
imagePullSecrets: []
storageClass: ""

## Please configure your PostgreSQL server first
## Jumpserver will not start the external PostgreSQL server.
##
externalDatabase:
engine: postgresql
host: postgresql-svc.middleware.svc.cluster.local
port: 5432
user: postgres
password: "uLwHDyYr"
database: jumpserver

## Please configure your Redis server first
## Jumpserver will not start the external Redis server.
##
externalSentinel:
{}
# hosts: mymaster/localhost:26379,localhost:26380,localhost:26381
# password: ""
# socketTimeout: 5

## Sentinel or Redis one of them must be configured.

externalRedis:
host: redis-svc.middleware.svc.cluster.local
port: 6379
password: "******"

serviceAccount:
## Specifies whether a service account should be created
create: false
## The name of the service account to use.
## If not set and create is true, a name is generated using the fullname template
name:

ingress:
enabled: true
annotations:
	cert-manager.io/cluster-issuer: sencrypt-prod
	# nginx.ingress.kubernetes.io/enable-cors: "true"
	# nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, OPTIONS, PUT, DELETE"
	# nginx.ingress.kubernetes.io/cors-allow-headers: "Authorization, Content-Type"
	# nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
	# nginx.ingress.kubernetes.io/cors-allow-origin: "*"
	# kubernetes.io/tls-acme: "true"
	kubernetes.io/ingress.class: nginx
	nginx.ingress.kubernetes.io/proxy-body-size: "4096m"
	nginx.ingress.kubernetes.io/server-snippets: |
	proxy_set_header Upgrade "websocket";
	proxy_set_header Connection "Upgrade";

hosts:
	- "jumpserver.example.com"
tls:
	- hosts:
		- jumpserver.example.com
	secretName: jumpserver.example.com

core:
enabled: true

labels:
	app.jumpserver.org/name: jms-core

config:
	## Generate a new random secret key by execute `cat /dev/urandom | tr -dc A-Za-z0-9 | head -c 50`
	secretKey: "TAcm23tDmN9n7cVCbgkDo6ln62qTuXYDKfLFYPFLd1y7DL5cS9"
	## Generate a new random bootstrap token by execute `cat /dev/urandom | tr -dc A-Za-z0-9 | head -c 24`
	bootstrapToken: "KX8k37hBbQjmKgRDR17QF7EB"
	## Enabled it for debug
	debug: false
	log:
	level: ERROR

replicaCount: 1

image:
	registry: docker.1ms.run
	pullPolicy: IfNotPresent

env:
	## See: https://docs.jumpserver.org/zh/master/admin-guide/env/#core
	SESSION_EXPIRE_AT_BROWSER_CLOSE: true
	# SESSION_COOKIE_AGE: 86400
	# SECURITY_VIEW_AUTH_NEED_MFA: true
	## Django CSRF_TRUSTED_ORIGINS need to be set to the domain name of the jumpserver (https://docs.jumpserver.org/zh/v3/installation/upgrade_notice/)
	DOMAINS: "192.168.142.56:33380"

livenessProbe:
	initialDelaySeconds: 90
	failureThreshold: 3
	timeoutSeconds: 5
	httpGet:
	path: /api/health/
	port: web

podSecurityContext:
	{}
	# fsGroup: 2000

securityContext:
	{}
	# capabilities:
	#   drop:
	#   - ALL
	# readOnlyRootFilesystem: true
	# runAsNonRoot: true
	# runAsUser: 1000

service:
	type: ClusterIP
	web:
	port: 8080

resources:
	{}
	## We usually recommend not to specify default resources and to leave this as a conscious
	## choice for the user. This also increases chances charts run on environments with little
	## resources, such as Minikube. If you do want to specify resources, uncomment the following
	## lines, adjust them as necessary, and remove the curly braces after 'resources:'.
	# limits:
	#   cpu: 1000m
	#   memory: 2048Mi
	# requests:
	#   cpu: 500m
	#   memory: 1024Mi

persistence:
	storageClassName: storage
	accessModes:
	- ReadWriteMany
	size: 100Gi
	annotations:
	"helm.sh/resource-policy": keep
	finalizers:
	- kubernetes.io/pvc-protection
	# subPath: ""
	# existingClaim: ""

volumeMounts: []

volumes: []

nodeSelector: {}

tolerations: []

affinity: {}

koko:
enabled: true

labels:
	app.jumpserver.org/name: jms-koko

config:
	log:
	level: ERROR

replicaCount: 1

image:
	registry: docker.1ms.run
	pullPolicy: IfNotPresent

env:
	[]
	## See: https://docs.jumpserver.org/zh/master/admin-guide/env/#koko
	# LANGUAGE_CODE: zh
	# REUSE_CONNECTION: true
	# ENABLE_LOCAL_PORT_FORWARD: true
	# ENABLE_VSCODE_SUPPORT: true

livenessProbe:
	initialDelaySeconds: 10
	failureThreshold: 3
	timeoutSeconds: 5
	httpGet:
	path: /koko/health/
	port: web

podSecurityContext:
	{}
	# fsGroup: 2000

securityContext:
	privileged: true
	# capabilities:
	#   drop:
	#   - ALL
	# readOnlyRootFilesystem: true
	# runAsNonRoot: true
	# runAsUser: 1000

service:
	type: ClusterIP
	web:
	port: 5000
	ssh:
	port: 2222

resources:
	{}
	## We usually recommend not to specify default resources and to leave this as a conscious
	## choice for the user. This also increases chances charts run on environments with little
	## resources, such as Minikube. If you do want to specify resources, uncomment the following
	## lines, adjust them as necessary, and remove the curly braces after 'resources:'.
	# limits:
	#   cpu: 100m
	#   memory: 128Mi
	# requests:
	#   cpu: 100m
	#   memory: 128Mi

persistence:
	storageClassName: storage
	accessModes:
	- ReadWriteMany
	size: 10Gi
	annotations:
	"helm.sh/resource-policy": keep
	finalizers:
	- kubernetes.io/pvc-protection
	# subPath: ""
	# existingClaim: ""

volumeMounts: []

volumes: []

nodeSelector: {}

tolerations: []

affinity: {}

lion:
enabled: true

labels:
	app.jumpserver.org/name: jms-lion

config:
	log:
	level: ERROR

replicaCount: 1

image:
	registry: docker.1ms.run
	pullPolicy: IfNotPresent

env:
	## See: https://docs.jumpserver.org/zh/master/admin-guide/env/#lion
	JUMPSERVER_ENABLE_FONT_SMOOTHING: true
	# JUMPSERVER_COLOR_DEPTH: 32
	# JUMPSERVER_ENABLE_WALLPAPER: true
	# JUMPSERVER_ENABLE_THEMING: true
	# JUMPSERVER_ENABLE_FULL_WINDOW_DRAG: true
	# JUMPSERVER_ENABLE_DESKTOP_COMPOSITION: true
	# JUMPSERVER_ENABLE_MENU_ANIMATIONS: true

livenessProbe:
	initialDelaySeconds: 90
	failureThreshold: 3
	timeoutSeconds: 5
	httpGet:
	path: /lion/health/
	port: web

podSecurityContext:
	{}
	# fsGroup: 2000

securityContext:
	{}
	# capabilities:
	#   drop:
	#   - ALL
	# readOnlyRootFilesystem: true
	# runAsNonRoot: true
	# runAsUser: 1000

service:
	type: ClusterIP
	web:
	port: 8081

resources:
	{}
	## We usually recommend not to specify default resources and to leave this as a conscious
	## choice for the user. This also increases chances charts run on environments with little
	## resources, such as Minikube. If you do want to specify resources, uncomment the following
	## lines, adjust them as necessary, and remove the curly braces after 'resources:'.
	# limits:
	#   cpu: 100m
	#   memory: 512Mi
	# requests:
	#   cpu: 100m
	#   memory: 512Mi

persistence:
	storageClassName: storage
	accessModes:
	- ReadWriteMany
	size: 50Gi
	annotations:
	"helm.sh/resource-policy": keep
	finalizers:
	- kubernetes.io/pvc-protection
	# subPath: ""
	# existingClaim: ""

volumeMounts: []

volumes: []

nodeSelector: {}

tolerations: []

affinity: {}

chen:
enabled: true

labels:
	app.jumpserver.org/name: jms-chen

config:
	log:
	level: ERROR

replicaCount: 1

image:
	registry: docker.1ms.run
	pullPolicy: IfNotPresent

env: []

livenessProbe:
	initialDelaySeconds: 60
	failureThreshold: 3
	timeoutSeconds: 5
	httpGet:
	path: /chen
	port: web

podSecurityContext:
	{}
	# fsGroup: 2000

securityContext:
	{}
	# capabilities:
	#   drop:
	#   - ALL
	# readOnlyRootFilesystem: true
	# runAsNonRoot: true
	# runAsUser: 1000

service:
	type: ClusterIP
	web:
	port: 8082

resources:
	{}
	## We usually recommend not to specify default resources and to leave this as a conscious
	## choice for the user. This also increases chances charts run on environments with little
	## resources, such as Minikube. If you do want to specify resources, uncomment the following
	## lines, adjust them as necessary, and remove the curly braces after 'resources:'.
	# limits:
	#   cpu: 100m
	#   memory: 128Mi
	# requests:
	#   cpu: 100m
	#   memory: 128Mi

persistence:
	storageClassName: storage
	accessModes:
	- ReadWriteMany
	size: 10Gi
	annotations:
	"helm.sh/resource-policy": keep
	finalizers:
	- kubernetes.io/pvc-protection
	# subPath: ""
	# existingClaim: ""

volumeMounts: []

volumes: []

nodeSelector: {}

tolerations: []

affinity: {}

xpack:
enabled: false

magnus:
labels:
	app.jumpserver.org/name: jms-magnus

config:
	log:
	level: ERROR

replicaCount: 1

image:
	registry: docker.1ms.run
	pullPolicy: IfNotPresent

env: []

livenessProbe:
	initialDelaySeconds: 10
	failureThreshold: 3
	timeoutSeconds: 5
	httpGet:
	path: /health
	port: web

podSecurityContext:
	{}
	# fsGroup: 2000

securityContext:
	{}
	# capabilities:
	#   drop:
	#   - ALL
	# readOnlyRootFilesystem: true
	# runAsNonRoot: true
	# runAsUser: 1000

service:
	type: ClusterIP
	web:
	port: 8088
	mysql:
	port: 33061
	mariadb:
	port: 33062
	redis:
	port: 63790
	postgresql:
	port: 54320
	sqlserver:
	port: 14330
	oracle:
	ports: 30000-30100

resources:
	{}
	## We usually recommend not to specify default resources and to leave this as a conscious
	## choice for the user. This also increases chances charts run on environments with little
	## resources, such as Minikube. If you do want to specify resources, uncomment the following
	## lines, adjust them as necessary, and remove the curly braces after 'resources:'.
	# limits:
	#   cpu: 100m
	#   memory: 512Mi
	# requests:
	#   cpu: 100m
	#   memory: 512Mi

persistence:
	storageClassName: storage
	accessModes:
	- ReadWriteMany
	size: 10Gi
	annotations:
	"helm.sh/resource-policy": keep
	finalizers:
	- kubernetes.io/pvc-protection
	# subPath: ""
	# existingClaim: ""

volumeMounts: []

volumes: []

nodeSelector: {}

tolerations: []

affinity: {}

xrdp:
labels:
	app.jumpserver.org/name: jms-xrdp

config:
	log:
	level: ERROR

replicaCount: 1

image:
	registry: registry.fit2cloud.com
	pullPolicy: IfNotPresent

env: []

livenessProbe:
	initialDelaySeconds: 10
	failureThreshold: 3
	timeoutSeconds: 5
	tcpSocket:
	port: rdp

podSecurityContext:
	{}
	# fsGroup: 2000

securityContext:
	{}
	# capabilities:
	#   drop:
	#   - ALL
	# readOnlyRootFilesystem: true
	# runAsNonRoot: true
	# runAsUser: 1000

service:
	type: ClusterIP
	rdp:
	port: 3390

resources:
	{}
	## We usually recommend not to specify default resources and to leave this as a conscious
	## choice for the user. This also increases chances charts run on environments with little
	## resources, such as Minikube. If you do want to specify resources, uncomment the following
	## lines, adjust them as necessary, and remove the curly braces after 'resources:'.
	# limits:
	#   cpu: 100m
	#   memory: 128Mi
	# requests:
	#   cpu: 100m
	#   memory: 128Mi

persistence:
	storageClassName: storage
	accessModes:
	- ReadWriteMany
	size: 50Gi
	annotations:
	"helm.sh/resource-policy": keep
	finalizers:
	- kubernetes.io/pvc-protection
	# subPath: ""
	# existingClaim: ""

volumeMounts: []

volumes: []

nodeSelector: {}

tolerations: []

affinity: {}

razor:
labels:
	app.jumpserver.org/name: jms-razor

config:
	log:
	level: ERROR

replicaCount: 1

image:
	registry: registry.fit2cloud.com
	pullPolicy: IfNotPresent

env: []

livenessProbe:
	initialDelaySeconds: 10
	failureThreshold: 3
	timeoutSeconds: 5
	httpGet:
	path: /razor/health/
	port: web

podSecurityContext:
	{}
	# fsGroup: 2000

securityContext:
	{}
	# capabilities:
	#   drop:
	#   - ALL
	# readOnlyRootFilesystem: true
	# runAsNonRoot: true
	# runAsUser: 1000

service:
	type: ClusterIP
	web:
	port: 8084
	rdp:
	port: 3389

resources:
	{}
	## We usually recommend not to specify default resources and to leave this as a conscious
	## choice for the user. This also increases chances charts run on environments with little
	## resources, such as Minikube. If you do want to specify resources, uncomment the following
	## lines, adjust them as necessary, and remove the curly braces after 'resources:'.
	# limits:
	#   cpu: 100m
	#   memory: 128Mi
	# requests:
	#   cpu: 100m
	#   memory: 128Mi

persistence:
	storageClassName: storage
	accessModes:
	- ReadWriteMany
	size: 50Gi
	annotations:
	"helm.sh/resource-policy": keep
	finalizers:
	- kubernetes.io/pvc-protection
	# subPath: ""
	# existingClaim: ""

volumeMounts: []

volumes: []

nodeSelector: {}

tolerations: []

affinity: {}

video:
labels:
	app.jumpserver.org/name: jms-video

config:
	log:
	level: ERROR

replicaCount: 1

image:
	registry: registry.fit2cloud.com
	pullPolicy: IfNotPresent

env: []

livenessProbe:
	initialDelaySeconds: 10
	failureThreshold: 3
	timeoutSeconds: 5
	httpGet:
	path: /video-worker/health/
	port: web

podSecurityContext:
	{}
	# fsGroup: 2000

securityContext:
	{}
	# capabilities:
	#   drop:
	#   - ALL
	# readOnlyRootFilesystem: true
	# runAsNonRoot: true
	# runAsUser: 1000

service:
	service:
	type: ClusterIP
	web:
	port: 9000

resources:
	{}
	## We usually recommend not to specify default resources and to leave this as a conscious
	## choice for the user. This also increases chances charts run on environments with little
	## resources, such as Minikube. If you do want to specify resources, uncomment the following
	## lines, adjust them as necessary, and remove the curly braces after 'resources:'.
	# limits:
	#   cpu: 100m
	#   memory: 128Mi
	# requests:
	#   cpu: 100m
	#   memory: 128Mi

persistence:
	storageClassName: storage
	accessModes:
	- ReadWriteMany
	size: 50Gi
	annotations:
	"helm.sh/resource-policy": keep
	finalizers:
	- kubernetes.io/pvc-protection
	# subPath: ""
	# existingClaim: ""

volumeMounts: []

volumes: []

nodeSelector: {}

tolerations: []

affinity: {}

web:
enabled: true

labels:
	app.jumpserver.org/name: jms-web

replicaCount: 1

image:
	registry: docker.1ms.run
	pullPolicy: IfNotPresent

env:
	# nginx client_max_body_size, default 4G
	CLIENT_MAX_BODY_SIZE: 4096m
	## See: https://github.com/jumpserver/docker-web/blob/master/init.sh#L37
	# USE_LB: 1, then nginx use 'proxy_set_header X-Forwarded-For $remote_addr'
	# USE_LB: 0, then nginx use 'proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for'
	USE_LB: 0

livenessProbe:
	initialDelaySeconds: 10
	failureThreshold: 3
	timeoutSeconds: 5
	httpGet:
	path: /api/health/
	port: web

podSecurityContext:
	{}
	# fsGroup: 2000

securityContext:
	{}
	# capabilities:
	#   drop:
	#   - ALL
	# readOnlyRootFilesystem: true
	# runAsNonRoot: true
	# runAsUser: 1000

service:
	type: ClusterIP
	web:
	port: 80

resources:
	{}
	## We usually recommend not to specify default resources and to leave this as a conscious
	## choice for the user. This also increases chances charts run on environments with little
	## resources, such as Minikube. If you do want to specify resources, uncomment the following
	## lines, adjust them as necessary, and remove the curly braces after 'resources:'.
	# limits:
	#   cpu: 100m
	#   memory: 128Mi
	# requests:
	#   cpu: 100m
	#   memory: 128Mi

persistence:
	storageClassName: storage
	accessModes:
	- ReadWriteMany
	size: 1Gi
	annotations:
	"helm.sh/resource-policy": keep
	finalizers:
	- kubernetes.io/pvc-protection
	# subPath: ""
	# existingClaim: ""

volumeMounts: []

volumes: []

nodeSelector: {}

tolerations: []

affinity: {}

```

修改 core 模块

1
2
3
kubectl -n jumpserver edit deployments.apps jms-k8s-jumpserver-jms-core

# 将 image 镜像名称修改为 jumpserver/core:v4.6.1-ce
面朝大海,春暖花开。
使用 Hugo 构建
主题 StackJimmy 设计