시놀로지 도커 설정

DM wiki
Kim135797531 (토론 | 기여)님의 2021년 10월 14일 (목) 16:14 판
(차이) ← 이전 판 | 최신판 (차이) | 다음 판 → (차이)
둘러보기로 이동 검색으로 이동

외부 노출 서버 (시놀로지 VMM)

  • alpine-virt-3.11.3

역할

  • 현재 IPv6 Cloudflare에 보고(갱신)
  • 웹 프록시

마운트 설정

  • alpine에 nfsmount 설치
    • apk add nfsmount
  • /etc/fstab에 다음 내용 추가
    • 맨 뒤의 _netdev와 0 0을 꼭 추가해야 한다 (안하면 네트워크 연결 전에 마운트 시도해서 부팅 안 됨)
      내부서버IP:/volume1/시놀로지폴더 /마운트경로 nfs nfsvers=3,rsize=524288,wsize=524288,ro,auto,nolock,_netdev 0 0
      

Cloudflare 연동 IP 등록

  • 일본 특성상 IPv4 포트가 매우 제한적으로 열려 있고, 10000번대 밑으로는 아예 열리지도 않음
  • IPv6는 이런 제한이 없음. 공유기가 각각의 머신에 뿌려주는 IP는 DHCP라기보다는 일본 인터넷에 직접 연결된 공인 IP.
    • IPv6는 설계시부터 모든 기기가 고유의 공인 IP를 갖는 형태로 설계됨. 다만 그 특성상 보안에 취약하므로, 영구 IPv6 주소와 별개로 임시 IPv6가 부여되어, 수시로 주소가 바뀜.
    • 주소 바뀜을 체크하여, Cloudflare DNS에 등록해 주는 역할 (v6 레코드인 AAAA를 업데이트 함)
    • 다음 파일을 alpine 서버의 /etc/init.d에 저장. (파일 이름 cloudflare-alpine.service)
      #!/sbin/openrc-run
      
      # $apk add python3
      # $apk add py3-requests
      # Copy this file into /etc/init.d/
      # Test by $rc-service cloudflare-alpine.service start
      # $rc-update add cloudflare-alpine.service
      # $rc-service cloudflare-alpine.service restart
      # $rc-update -u
      
      description="Update Cloudflare DNS"
      
      command="/usr/bin/python3 /root/cloudflare-alpine.py"
      pidfile="/var/run/cloudflare/cloudflare.pid"
      command_background="yes"
      
      depend() {
              need net
              need nfsmount
              need sshd
              after iptables
      }
      
      start_pre() {
              ebegin "Starting cloudflare"
              mkdir -p /var/run/cloudflare || return 1
      }
      
      실제 업데이트를 수행하는 Python 파일을 위의 서비스 스크립트에 지정된 위치로 잘 저장
      #!/usr/bin/env python
      # -*- coding: utf-8 -*-
      
      import os
      import requests
      import json
      from time import sleep
      
      CLOUDFLARE_KEY = 'XXX'
      CLOUDFLARE_ZONE_ID = 'XXX'
      CLOUDFLARE_DNS_TOKEN = 'XXX'
      
      CLOUDFLARE_HEADER = {
          'Authorization': 'Bearer {}'.format(CLOUDFLARE_DNS_TOKEN),
          'Content-Type': 'application/json'
      }
      DNS_GET_URL = 'https://api.cloudflare.com/client/v4/zones/{}/dns_records'.format(CLOUDFLARE_ZONE_ID)
      
      device = 'eth0'
      addr_file = '/tmp/cloudflare.addr6'
      
      def check():
          addr = ''
          try:
              with open(addr_file, 'r') as f:
                  addr = f.read()
          except:
              pass
      
          get_addr_cmd = "ip -6 addr list scope global $device | " \
                         "grep -v ' fd' | " \
                         "grep -v 'deprecated' | " \
                         "grep -v 'mngtmpaddr' | " \
                         "head -n 2 | " \
                         "tail -n 1"
      
          new_addr = os.popen(get_addr_cmd).read()
          new_addr = new_addr.split()[1]
          new_addr = new_addr.split('/')[0]
      
      
          if addr == new_addr:
              print('same ip {}'.format(new_addr))
          else:
              r = requests.get(DNS_GET_URL, headers=CLOUDFLARE_HEADER)
              j = json.loads(r.text)
      
              for item in j['result']:
                  dns_put_url = DNS_GET_URL + '/' + item['id']
                  new_data = dict(
                      type=item['type'],
                      name=item['name'],
                      content=new_addr,
                  )
                  if item['type'] == 'AAAA':
                      new_data['proxied'] = True
                      requests.put(dns_put_url, data=json.dumps(new_data), headers=CLOUDFLARE_HEADER)
                  elif item['type'] == 'TXT':
                      new_data['content'] = 'v=spf1 {} ~all'.format(new_addr)
                      requests.put(dns_put_url, data=json.dumps(new_data), headers=CLOUDFLARE_HEADER)
      
              with open(addr_file, 'w') as f:
                  f.write(new_addr)
                  f.close()
              print('updated ip {}'.format(new_addr))
              
      
      while True:
          check()
          sleep(60)
      
    • 그 후 서비스 등록
      rc-update add cloudflare-alpine.service
      rc-service cloudflare-alpine.service restart
      rc-update -u
      

Nginx 웹 프록시

  • Cloudflare로부터 오는 HTTPS 요청을 처리하여, 내부 서버에 전달
  • 처음 도입 목적은 Cloudflare의 IPv6 over IPv4 기능을 이용하기 위함
  • 생각해보니 외부/내부를 분리하여 보안성도 높아지고, 도커를 이용한 로드밸런서 구현 및 포트 번호 추상화가 가능해져 채택
  • /etc/nginx/conf.d는 시놀로지에 config를 등록해 놓고 폴더째로 마운트
  • wiki-proxy.conf 예시
    • proxy_set_header를 설정해야 내부 서버에서 보이는 접속 요청 IP가 실제 사용자의 IP로 제대로 뜬다
    • 내부 서버사이의 통신은 http로 하여 속도 향상 (방화벽 설정은 따로)
    • TODO: upstream 요청 처리가 60초 이상 걸려도 취소 안 하게 설정하기? wiki의 restbase나 mathoid가 고장나는 이유가 이것 때문인 듯
      server {
          listen   80;
          listen   [::]:80;
          server_name wiki.dong-min.kim;
              
          location / {
              return 301 https://$server_name$request_uri;
          }
      }
      
      server {
          listen 443 ssl;
      	listen [::]:443 ssl;
      
          ssl_certificate pem파일;
          ssl_certificate_key key파일;
              
      	server_name wiki.dong-min.kim;
          server_tokens off;
         
      	location / {
              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_set_header  X-Forwarded-Host $server_name;
              proxy_set_header  Client-IP $remote_addr;
      		proxy_pass http://내부 서버 주소:포트;
      	}
      }
      

웹 데몬

Nginx (도커)

역할

  • HTTP 요청을 받아서
    • 직접 처리(static)하거나
    • PHP 인터프리터로 보내거나
    • 다른 웹 데몬(Node.js, Python, ...)으로 다시 proxy한다

레포지토리

컨테이너 설정

  • 포트 설정
    • (외부 노출 서버 Nginx Proxy 포트 설정):(그대로)
  • 볼륨 설정
    • htpasswd -> (비공개)/htpasswd
    • passwd -> /etc/passwd
    • www -> (비공개)/www/
    • nginx/conf.d/ -> /etc/nginx/conf.d/
    • nginx/nginx.conf -> /etc/nginx/nginx.conf
  • 환경 변수
    • (기본값)

인터프리터

PHP (도커)

역할

  • PHP 데몬
  • FPM(FastCGI Process Manager) 사용하여 동적 워커 생성

레포지토리

컨테이너 설정

  • 포트 설정
    • (비공개):9000
    • 참고로 www.conf에서 다른 포트로 설정해도 php-fpm.d/zz-docker.conf에서 무조건 기본값으로 바꿈
  • 볼륨 설정
    • php.ini -> /usr/local/etc/php/php.ini
    • www.conf -> /usr/local/etc/php-fpm.d/www.conf
    • www.conf -> /usr/local/etc/php-fpm.d/www.conf.default
    • www -> (비공개)/www/
    • php-패스wd -> /이티시/패스wd
  • 환경 변수
    • (기본값)

php.ini 설정

  • max_execution_time = 30
  • max_input_time = 60
  • memory_limit = 512M
  • post_max_size = 10240M
  • upload_max_filesize = 10240M
  • default_socket_timeout = 30
  • extension gd2, mysqli 활성화

www.conf 설정

  • 여기 설정은 주로 미디어위키의 VisualEditor 확장 기능 + Math 수식 입력기를 사용할 때 Restbase 서버에 동시에 많은 요청을 보내는 것을 처리하기 위해 필요함
  • pm.max_children = 16
  • pm.start_servers = 16
  • pm.min_spare_servers = 16
  • pm.max_spare_servers = 16
  • pm.process_idle_timeout = 30s

데이터베이스

MySQL (도커)

역할

  • 워드프레스, 미디어위키에 사용하기 위한 범용 데이터베이스

레포지토리

컨테이너 설정

  • 포트 설정
    • (외부 노출 서버 Nginx Proxy 포트 설정 시작 번호-2):(그대로)
  • 볼륨 설정
    • MySQL 실제 DB 파일 폴더 -> /var/lib/mysql/
    • mysql.cnf -> /etc/mysql/conf.d/mysql.cnf
  • 환경 변수
    • (기본값)

Cassandra (도커)

역할

  • 미디어위키의 API 백엔드 Restbase에서 사용하기 위한 고성능 데이터베이스

레포지토리

컨테이너 설정

  • 포트 설정
    • (비공개):9042
  • 볼륨 설정
    • Cassandra 실제 DB 파일 폴더 -> /var/lib/cassandra/
    • Cassandra 설정 폴더 -> /etc/cassandra/
  • 환경 변수
    • CASSANDRA_BROADCAST_ADDRESS = (호스트 주소 - 집 안의 NAS IP)

cassandra-env.sh 설정

cassandra.yaml 설정

응용 프로그램(애플리케이션) - Nginx 종속

워드프레스 (파일)

역할

wp-컨피그.php

  • FTPS 설정 (SFTP가 아니다)
  • 프록시된 요청을 처리하기 위한 추가 설정
    • $_SERVER['HTTPS']='on'; 구절만 있으면 된다
    • 나머지는 프록시 서버 측의 설정이 잘못된 상태일 때 필요했었다
  • define('FTP_HOST', '내부 서버 주소:내부 포트 번호');
    define('FTP_USER', 'wp-ftps');
    define('FTP_PASS', '');
    define('FTP_SSL', true);
    define('FTP_BASE', '(비공개)/wordpress/');
    define('FTP_CONTENT_DIR', '(비공개)/wp-content/');
    define('FTP_PLUGIN_DIR ', '(비공개)/wp-content/plugins/');
    
    if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
        $_SERVER['HTTPS']='on';
        // define('RELOCATE', true); 
        // define('WP_HOME', 'https://dong-min.kim');
        // define('WP_SITEURL', 'https://dong-min.kim');
    
    /*
    if ( ! empty( $_SERVER['HTTP_X_FORWARDED_HOST'] ) ) {
        $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
    }
    */
    

미디어위키 (파일)

역할

확장 기능

  • VisualEditor, Math, SyntaxHighlight
    • VisualEditor와 Math를 연동할 때, 수식 편집기에서 동적 업데이트 횟수에 비례해서 서버가 느려지는 현상 발생
    • Math 확장 기능에서 제공하는 MWLatexNode.js가 수식이 업데이트 될 때마다 과거 기록을 지우지 않고 전부 갖고 있는 버그가 원인
    • 그 기록들도 매 번 의미없는 업데이트를 하느라 서버가 느려짐
    • 임시 해결 방법
      • Math 확장 기능의 modules/ve-math/ve.ce.MWLatexNode.js 파일의 맨 아래 쪽에 다음 항목을 추가하여, 강제 업데이트를 방지
      • 이거 해도 새로 고침은 하지만, 적어도 Mathoid 서버에 새로 요청 하지는 않는다 (캐시 사용)
      • /**
         * @inheritdoc ve.ce.GeneratedContentNode
         */
        ve.ce.MWLatexNode.prototype.onGeneratedContentNodeUpdate = function ( staged ) {
        	// 꼼수... this.root가 있을 때만 업데이트함
        	// this.root가 있는 노드는 실제 글 속의 math 노드 (다이얼로그 속은 root 없음)
        	if (this.root)
        		this.update( undefined, staged );
        };
        
Nginx config
server {
    listen   ?????;
    listen   [::]:?????;
    server_name wiki.dong-min.kim;

    access_log /var/log/nginx/wiki.access.log main;
    error_log /var/log/nginx/wiki.error.log;

	charset utf-8;
	root ?????;
	index index.html index.htm index.php;

	client_max_body_size 10240M;
	
    # Avoid 504 HTTP Timeout Errors
    proxy_connect_timeout       605;
    proxy_send_timeout          605;
    proxy_read_timeout          605;
    send_timeout                605;
    keepalive_timeout           605;
	fastcgi_read_timeout		605;
    
	location ~ ^/w/(index|load|thumb|opensearch_desc|api|rest|img_auth)\.php$ {
		include fastcgi.conf;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
		fastcgi_pass ?????:?????;
	}

	# Images
	location /w/images {
		# Separate location for images/ so .php execution won't apply
		try_files $uri 404;		
	}
	location /w/images/deleted {
		# Deny access to deleted images folder
		deny all;
	}
	# MediaWiki assets (usually images)
	location ~ ^/w/resources/(assets|lib|src) {
		try_files $uri 404;
		add_header Cache-Control "public";
		expires 7d;
	}
	# Assets, scripts and styles from skins and extensions
	location ~ ^/w/(skins|extensions)/.+\.(css|js|gif|jpg|jpeg|png|svg)$ {
		try_files $uri 404;
		add_header Cache-Control "public";
		expires 7d;
	}

	# License and credits files
	location ~ ^/w/(COPYING|CREDITS)$ {
		default_type text/plain;
	}

	## Uncomment the following code if you wish to use the installer/updater
	## installer/updater
	# location /w/mw-config/ {
	# 	# Do this inside of a location so it can be negated
	# 	location ~ \.php$ {
    #       include fastcgi.conf;
	# 		fastcgi_param SCRIPT_FILENAME $document_root/w/mw-config/$fastcgi_script_name;
	# 		fastcgi_pass ?????:????; # or whatever port your PHP-FPM listens on
	# 	}
	# }
	
	# Handling for Mediawiki REST API, see [[mw:API:REST_API]]
	location /w/rest.php/ {
		try_files $uri $uri/ /w/rest.php?$query_string;
	}

	# Handling for the article path (pretty URLs)
	location /view/ {
		rewrite ^/view/(?<pagename>.*)$ /w/index.php;
	}


	# Allow robots.txt in case you have one
	location = /robots.txt {
	}

	location = /favicon.ico {
	}

	location / {
		rewrite ^/(.*)$ /view/$1 redirect;
	}

}

LocalSettings.php

<?php

// ... (생략)

$wgArticlePath = "/view/$1";
$wgUsePathInfo = true;
$wgScriptExtension = ".php";

$wgMathValidModes[] = 'mathml';
$wgDefaultUserOptions['math'] = 'mathml';
$wgMathFullRestbaseURL = 'https://wiki-restbase.dong-min.kim/wiki.dong-min.kim/';
$wgMathMathMLUrl = 'https://wiki-mathoid.dong-min.kim';

# START VisualEditor Setting
$wgGroupPermissions['user']['writeapi'] = true;
wfLoadExtension( 'Parsoid', 'vendor/wikimedia/parsoid/extension.json' );
# END VisualEditor Setting

?>

미디어위키 Restbase (도커)

  • 이 Restbase를 비롯하여 아래의 Parsoid, Mathoid는 미디어위키에서 Visual Editor + Math 확장 기능을 사용하기 위해 설치함
  • 원래 미디어위키에서 무료로 제공하는 기본 Restbase서버와 Mathoid 서버를 이용해도 되지만, 속도가 정말 끔찍하게 느려서 직접 구축
  • 제대로 된 문서가 없어서 구축 삽질 엄청 했다... 코드 안에 mediawiki url 하드코딩이 너무 많아 뭐가 문제인지 발견하기가 힘들었다

역할

  • 미디어위키의 API를 처리하기 위한 REST 처리 서버
  • REST로 받은 요청을 내부 독립 서버 (Parsoid, Mathoid 등)에 전달 및 반환 결과 캐싱
  • 기본 DB 세팅인 SQLite는 동시에 많은 요청이 들어오면 db파일이 죽는 경우가 있어서, Cassandra 세팅으로 바꿈
  • Node.js

레포지토리

컨테이너 설정

  • 포트 설정
    • (비공개):7231
  • 볼륨 설정
    • config.yaml -> /mount/config.yaml
  • 환경 변수
    • (기본값)

미디어위키 Parsoid (도커)

역할

  • 미디어위키의 Visual Editor 등에서 html <-> wikitext 간 변환 등을 위해 문서 실시간 파싱 등을 처리
  • Node.js

레포지토리

컨테이너 설정

  • 포트 설정
    • (비공개):8000
    • (비공개):8001
  • 볼륨 설정
    • config.yaml -> /mount/config.yaml
  • 환경 변수
    • (기본값)

미디어위키 Mathoid (도커)

역할

  • 미디어위키의 Math 확장 기능에서 사용
  • LaTeX 문법을 svg, png 등으로 변환
  • Node.js 10
  • pm2로 서비스화 시킴

레포지토리

컨테이너 설정

  • 포트 설정
    • (비공개):10042
  • 볼륨 설정
    • config.yaml -> /mount/config.yaml
  • 환경 변수
    • (기본값)

Deluge (토렌트) (도커)

역할

  • 토렌트 서버
  • 컨테이너를 완전히 OpenVPN으로 감싸서, VPN 등록이 되지 않은 상태에서는 네트워크 접속을 원천 차단
  • *.ovpn 파일은 config 폴더에 넣으면 됨

레포지토리

컨테이너 설정

  • 포트 설정
    • (기본값)
  • 볼륨 설정
    • config/ -> /config
    • (자료 저장 폴더) -> /data
  • 환경 변수
    • NAME_SERVERS = 1.1.1.1
    • LAN_NETWORK = 호스트의 아이피 말고 호스트의 서브넷 (xxx.xxx.xxx.xxx/xx)
    • ENABLE_PRIVOXY = yes
    • PGID = 100
    • PUID = 1026
    • VPN_PROV = custom
    • VPN_ENABLED = yes
    • HOME = /config/home

Gitlab

역할

  • 사설 Git 서버

레포지토리

컨테이너 설정

  • 포트 설정
    • (비공개):22
    • (비공개):80
    • (비공개):443
    • (비공개):5005
  • 볼륨 설정
    • gitlab 실제 자료 폴더 -> /var/opt/gitlab/
    • gitlab 설정 폴더 -> /etc/gitlab/

gitlab.rb 설정

  • external_url 'https://git.dong-min.kim'
  • gitlab_rails['gitlab_ssh_host'] = 'kasumi.synology.me'
  • gitlab_rails['time_zone'] = 'Asia/Seoul'
  • gitlab_rails['gitlab_default_theme'] = 10 # dark(default) = 2, red = 9, light red = 10
  • gitlab_rails['trusted_proxies'] = ['시놀로지IP', '프록시IP']
  • gitlab_rails['gitlab_shell_ssh_port'] = 64100
  • registry_external_url 'https://registry.dong-min.kim'
  • gitlab_rails['registry_enabled'] = true
  • unicorn['worker_processes'] = 2
  • sidekiq['concurrency'] = 3 # default 25
  • nginx['gzip_enabled'] = false
  • nginx['listen_port'] = 80
  • nginx['listen_https'] = false
  • registry_nginx['enable'] = true
  • registry_nginx['gzip_enabled'] = false
  • registry_nginx['listen_port'] = 5005
  • registry_nginx['listen_https'] = false

Gitlab Runner

역할

  • Gitlab의 CI/CD 실행용 Runner

레포지토리

컨테이너 설정

  • 파일 설정
    • docker.sock -> /var/run/docker.sock
  • 볼륨 설정
    • gitlab runner 설정 폴더 -> /etc/gitlab-runner
  • 주의점
    • 시놀로지 docker를 쓰려면 시놀로지의 docker.sock이 필요하다.
      • 먼저 시놀로지 측에서 sudo ln -s /var/run/docker.sock /volume1/docker/docker.sock 으로 심볼릭 링크 생성
      • Gitlab Runner 컨테이너 생성할 때 위 파일 링크가 안 됨
      • 먼저 아무 파일이나 링크해서 일단 컨테이너 생성
      • 컨테이너 설정 백업해서, 그 파일 안에서 링크 수정
      • 수정한 컨테이너 설정을 다시 로드
    • 컨테이너 실행 후 gitlab-runner register 명령어로 gitlab 등록

kb_apart (광규 외주) (도커)

역할

  • 광규 외주 프로그램 - 아파트 시세 확인
  • gliderlabs/alpine:3.3 기반 (glibc 설치용)
  • Miniconda3 설치
  • Python 3.7.5 + Flask

레포지토리

컨테이너 설정

  • 포트 설정
    • (비공개):5000
  • 볼륨 설정
    • 법정동.txt -> /var/lib/kb_apart/utils/법정동.txt
  • 환경 변수
    • CONDA_ENV_NAME = kb_apart

Internet Speed Logger (도커)

역할

  • 일정 주기마다 Speedtest 사이트로 속도 측정해서 그래프로 표시
  • 자꾸 집 인터넷 QoS 걸리는 것 같아서 설치함

레포지토리

컨테이너 설정

  • 포트 설정
    • (비공개):3000
  • 볼륨 설정
    • internet speed logger 설정 폴더 -> /usr/src/app/data
  • 환경 변수
    • CHECK_PERIOD_MINUTES = 30

응용 프로그램(애플리케이션) - 독립형

busybox (도커)

역할

  • 도커 게스트 관점에서 바라보는 파일 권한 확인, 간단한 명령어 (curl이나 ping 등) 실행용

레포지토리

컨테이너 설정

  • 포트 설정
    • (없음)
  • 볼륨 설정
    • www -> (비공개)/www/
  • 환경 변수
    • (기본값)

openvpn (도커)

역할

  • OpenVPN Client 를 수행해서, 그 결과를 SOCKS5 프록시 서버로 통신 가능
  • OpenVPN to SOCKS5/HTTP Proxy Docker Image
  • 시놀로지 도커에서 자꾸 IPV6 설정 실패하니, ovpn 파일 안의
    • ovpn 파일 안에 proto udp를 proto udp4로
    • setenv UV_IPV6 yes 주석처리

레포지토리

컨테이너 설정

  • 포트 설정
    • (비공개):1080 tcp
    • (비공개):1080 udp
  • 볼륨 설정
    • (openvpn 설정폴더)/vpn -> /vpn
    • /dev/net/tun -> /dev/net/tun (필요없을수도?)
  • 환경 변수
    • OPENVPN_CONFIG = /vpn/설정파일.ovpn

권한 관련 팁

Nginx, PHP의 404 Not found 오류는 권한 문제일 때가 많다. 도커에서 실행중이라고 했을 때 살펴볼 것은

  • 컨테이너 내에서 Nginx나 PHP를 실행하고 있는 uid는 무엇인가?
    • 해당 uid가 파일을 읽을 수 있는가? 쓸 수 있는가?
  • uid가 속해 있는 그룹 확인 (gid)
    • 해당 gid가 파일을 읽을 수 있는가? 쓸 수 있는가?
  • 권한이 있는데도 접근이 안 되는 경우
    • 루트 디렉터리에서부터 시작해서, 해당 파일이 있는 디렉터리까지의 경로까지 중에서 현재 uid 또는 gid로 실행 권한을 얻을 수 있는지 확인할 것.
    • 읽기, 쓰기 권한이 아니라 실행 권한이다.
  • 겉으로 보기에는 같은 www-data이더라도 컨테이너에 따라 id는 다를 수 있다.
  • 시놀로지의 경우 NFS 서버로 마운트한 클라이언트에서 본 권한과도 다를 수 있기 때문에 주의
  • LDAP 설정해서 동기화 하려 했으나 귀찮아서 일단 보류

집 Docker Swarm 삽질

GUI 프로그램

Portainer (9000)

  • 가능
    • gitlab 연동 가능
    • gitlab 레지스트리에서 이미지 push/pull 가능
    • 터미널 실행 가능
  • 불가
    • gitlab 레지스트리 목록 보기 불가
    • 이미지에서 마우스로 컨테이너 실행 불가

Swarmpit (888)

  • 가능
    • gitlab 연동 가능
    • gitlab 레지스트리에서 이미지 push/pull 가능
    • gitlab 레지스트리 목록 보기 가능
    • 이미지에서 마우스로 컨테이너 실행 가능
  • 불가
    • 터미널 실행 불가