소개
오늘날의 서버 인프라 머신은 온프레미스 데이터센터, 프라이빗 데이터센터 또는 퍼블릭 클라우드 데이터센터에 있습니다. 이러한 머신은 물리적 베어메탈 머신, 하이퍼바이저가 있는 가상 머신(VM) 또는 물리적 또는 가상 머신 위에 있는 도커 컨테이너와 같은 소형 컨테이너입니다. 이러한 머신은 물리적으로 로컬 실험실에 있을 수 있습니다. 프라이빗 데이터센터 시나리오에서는 자체 조달한 호스트가 타사 데이터센터에서 공유되는 물리적 공간에 배치되고 원격으로 연결됩니다. 반면 AWS, GCP, Azure, OCI와 같은 퍼블릭 데이터센터에서는 원격으로 연결되는 확장성이 높은 요구 사항을 위해 머신을 예약하거나 온디맨드 방식으로 생성합니다. 이러한 인프라는 각각 확장성, 보안, 안정성, 관리 및 비용 측면에서 고유한 장점을 가지고 있습니다.
제품 개발 환경 팀에서는 SDLC 프로세스 중에 많은 서버가 필요할 수 있습니다. Xen Server와 함께 자체 물리적 머신이 있는 프라이빗 데이터 센터를 선택했다고 가정해 보겠습니다. 이제 문제는 클라우드 환경과 유사하게 간결하고 민첩한 프로세스를 통해 프로비저닝 또는 종료를 위해 VM 수명주기를 어떻게 관리할 것인가 하는 것입니다.
이 문서는 동적 인프라 환경을 쉽게 구축할 수 있도록 기본 인프라 모델, 아키텍처, 최소 API 및 샘플 코드 스니펫을 제공하는 것을 목표로 합니다.
혜택
먼저 이 서버의 인프라 프로세스에서 따르는 일반적인 단계의 순서를 이해해 보겠습니다. 다음과 같이 기억하시면 됩니다.
-
- IT 부서의 새로운 기계 조달
- 호스트 가상화 - IT 부서에서 Xen Server 설치 및 VM 템플릿 만들기
- 개발 및 테스트 팀이 (예: JIRA) 티켓을 통해 IT 부서에 요청하는 정적 가상 머신
- 수신된 VM IP를 데이터베이스 또는 정적 파일에 유지하거나 Jenkins config.xml과 같은 구성 파일 또는 CI 도구에 하드코딩합니다.
- 제품을 설치하는 데 사용하기 전에 VM의 상태를 모니터링하여 상태가 양호한지 확인합니다.
- 서버 설치 전후에 정리 또는 제거하기
- 제품을 설치하기 전에 Windows에서 레지스트리 정리가 필요할 수 있습니다.
- 특정 영역이나 팀에 VM을 고정 할당하거나 엔지니어 전용으로 할당했을 수 있습니다.
그렇다면 어떻게 하면 이 프로세스를 더 간결하고 민첩하게 만들 수 있을까요? 간단한 자동화를 통해 위의 대부분의 단계를 제거할 수 있을까요?
예. 저희 환경에서는 1000개 이상의 가상 머신이 있었고 주로 다음과 같은 목표를 달성하려고 했습니다.
"테스트 실행 중 필요에 따라 일회용 VM을 온디맨드 방식으로 사용할 수 있습니다. 정기적인 테스트 주기로 Windows 정리 문제를 해결하세요."
아래에서 볼 수 있듯이 동적 가상 머신 서버 관리자 API 서비스를 사용하면 8단계 중 6단계를 제거할 수 있으며 전체 제품 팀에 대한 무제한 인프라 보기를 제공합니다. 처음 두 단계인 가상화 조달과 호스트 가상화만 필요합니다. 사실상 시간과 비용을 절약할 수 있습니다!

인프라를 확보하는 일반적인 흐름
동적 인프라 모델
아래 그림은 일반적인 서버 제품 환경에 대해 80%의 도커 컨테이너, 15%를 동적 VM으로, 5%를 특수한 경우를 위한 정적 풀링된 VM으로 구성하는 인프라를 제안합니다. 이 배분은 사용자 환경에 가장 적합한 것을 기준으로 조정할 수 있습니다.

인프라 모델
이제부터 동적 가상 머신 서버 관리자 부분에 대해 자세히 설명하겠습니다.
동적 서버 관리자 아키텍처
동적 VM 서버 관리자에서는 아래 REST API를 노출할 수 있는 간단한 API 서비스를 제공하며 자동화된 프로세스의 어느 곳에서나 사용할 수 있습니다. 기술 스택에서 볼 수 있듯이 실제 XenServer 호스트에서 VM을 생성하는 데는 Python 3 및 Python 기반 Xen API가 사용됩니다. REST 서비스 계층 생성에는 Flask가 사용되고 있습니다. OS는 윈도우2016, 센토스7, 센토스8, 데비안10, 우분투18, 오엘8, 수세15 등 제품이 지원하는 모든 플랫폼이 될 수 있습니다.

동적 가상 머신 서버 관리자 아키텍처
가상 머신의 이력을 저장하여 사용량과 프로비저닝 또는 종료까지의 시간을 추적하여 추가 분석이 가능합니다. json 문서 저장을 위해 nosql 문서 데이터베이스인 Couchbase 엔터프라이즈 서버를 사용할 수 있습니다.
간단한 REST API
| 방법 | URI(들) | 목적 |
| GET | /showall | 모든 VM을 json 형식으로 나열합니다. |
| GET | /겟어블카운트/ | 주어진 에 대해 사용 가능한 VM 개수 목록을 가져옵니다. |
| GET | /getservers/?os=
/getservers/?os=&count= /getservers/?os=&count=&cpus=&mem= /getservers/?os=&expiresin= |
의 VM이 주어진 프로비저닝입니다.
CPU 수와 메모리 크기도 지원할 수 있습니다. 만료인 매개 변수를 몇 분 단위로 설정하여 VM의 만료(자동 종료)를 가져옵니다. |
| GET | /releaseservers/?os=
/releaseservers/?os=&count= |
의 주어진 VM을 종료합니다. |
동적 VM 대상 Xen 호스트의 사전 요구 사항
- 대상 동적 VM Xen 호스트 식별
- VM 템플릿 복사/생성
- IP 재활용을 위해 이러한 Xen 호스트를 별도의 VLAN/서브넷으로 이동(IT 부서와 협력)합니다.
구현
높은 수준에서
- 각 REST API에 함수 만들기
- 공통 서비스를 호출하여 다양한 REST 작업을 수행합니다.
- Xen 세션 생성, 레코드 가져오기, 템플릿에서 VM 복제, 올바른 디스크 연결, VM 생성 및 IP 수신 대기, VM 삭제, 디스크 삭제에 대해 이해합니다.
- VM 만료에 대한 스레드 자동 시작
- .ini 형식과 같은 일반적인 구성 읽기
- Couchbase 데이터베이스 작업 이해 및 문서 저장
- 필요한 OS 및 매개 변수로 모든 API를 테스트하세요.
- 문제가 있는 경우 문제 해결
- 적은 수의 Xen 호스트로 POC 수행
아래 코드 스니펫을 참고하면 더 쉽게 이해할 수 있습니다.
API 생성
|
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 |
@앱.경로('/showall/') @앱.경로("/쇼월") def 쇼올_서비스(os=없음): 카운트, _ = GET_ALL_XEN_HOSTS_COUNT(os) 로그.정보("--> count: {}".형식(카운트)) all_vms = {} 에 대한 xen_host_ref in 범위(1, 카운트 + 1): 로그.정보("xen_host_ref=" + str(xen_host_ref)) all_vms[xen_host_ref] = 수행_서비스(xen_host_ref, 서비스_이름='listvms', os=os) 반환 json.덤프(all_vms, 들여쓰기=2, sort_keys=True) @앱.경로('/겟어블카운트/') @앱.경로('/겟어블카운트') def 사용 가능 횟수 서비스(os='centos'): """ 사용 가능한 개수를 계산합니다: 총 CPU, 총 메모리 가져오기 무료 CPU, 무료 메모리 확보 할당된 모든 VM - CPU 및 메모리 가져오기 각 OS 템플릿 가져오기 - CPU 및 메모리 사용 가능한 카운트1 = (사용 가능한 CPU - VM CPU)/OS_Template_CPUs 사용 가능한 카운트2 = (사용 가능한 메모리 - VM 메모리)/OS_템플릿_메모리 반환 min(count1,count2) """ 카운트, available_counts, xen_hosts = GET_ALL_AVAILABLE_COUNT(os) 로그.정보("{},{},{},{}".형식(카운트, available_counts, xen_hosts, reserved_count)) 만약 카운트 > reserved_count: 카운트 -= reserved_count 로그.정보("예약된 개수가 적습니다: {},{},{},{}".형식(카운트, available_counts, xen_hosts, reserved_count)) 반환 str(카운트) # /getservers/username?count=number&os=centos&ver=6&expiresin=30 @앱.경로('/getservers/') def getservers_service(사용자 이름): 글로벌 reserved_count 만약 요청.args.get('count'): vm_count = int(요청.args.get('count')) else: vm_count = 1 os_name = 요청.args.get('os') 만약 요청.args.get('cpus'): cpus_count = 요청.args.get('cpus') else: cpus_count = "default" 만약 요청.args.get('mem'): mem = 요청.args.get('mem') else: mem = "default" 만약 요청.args.get('만료'): exp = int(요청.args.get('만료')) else: exp = 최대_만료_분 만약 요청.args.get('format'): 출력_포맷 = 요청.args.get('format') else: 출력_포맷 = "servermanager" xhostref = 없음 만약 요청.args.get('xhostref'): xhostref = 요청.args.get('xhostref') reserved_count += vm_count 만약 xhostref: 로그.정보("--> 주어진 xenhost의 VM" + xhostref) vms_ips_list = 수행_서비스(xhostref, 'createvm', os_name, 사용자 이름, vm_count, cpus=cpus_count, 최대 메모리=mem, 만료_분=exp, 출력_포맷=출력_포맷) 반환 json.덤프(vms_ips_list) ... # /releaseservers/{사용자 이름} @앱.경로('/releaseservers//') @앱.경로('/releaseservers/') def 릴리즈서버_서비스(사용자 이름): 만약 요청.args.get('count'): vm_count = int(요청.args.get('count')) else: vm_count = 1 os_name = 요청.args.get('os') delete_vms_res = [] 에 대한 vm_index in 범위(vm_count): 만약 vm_count > 1: vm_name = 사용자 이름 + str(vm_index + 1) else: vm_name = 사용자 이름 xen_host_ref = get_vm_existed_xenhost_ref(vm_name, 1, 없음) 로그.정보("xhost_ref="에서 삭제할 VM" + str(xen_host_ref)) 만약 xen_host_ref != 0: delete_per_xen_res = 수행_서비스(xen_host_ref, 'deletevm', os_name, vm_name, 1) 에 대한 DELETED_VM_RES in delete_per_xen_res: delete_vms_res.추가(DELETED_VM_RES) 만약 len(delete_vms_res) < 1: 반환 "오류: VM " + 사용자 이름 + "존재하지 않음" else: 반환 json.덤프(delete_vms_res, 들여쓰기=2, sort_keys=True) def 수행_서비스(xen_host_ref=1, 서비스_이름='list_vms', os="centos", vm_prefix_names="", number_of_vms=1, cpus="default", 최대 메모리="default", 만료_분=최대_만료_분, 출력_포맷="servermanager", 시작_접미사=0): xen_host = get_xen_host(xen_host_ref, os) ... def 메인(): # 옵션 = parse_arguments() set_log_level() 앱.실행(호스트='0.0.0.0', debug=True) |
Xen 생성 세션
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def get_xen_session(xen_host_ref=1, os="centos"): xen_host = get_xen_host(xen_host_ref, os) 만약 not xen_host: 반환 없음 URL = "http://" + xen_host['host.name'] 로그.정보("\nXen 서버 호스트: " + xen_host['host.name'] + "\n") 시도: 세션 = XenAPI.세션(URL) 세션.xenapi.로그인_로_비밀번호(xen_host['host.user'], xen_host['host.password']) 예외 XenAPI.실패 as f: 오류 = "세션을 획득하지 못했습니다: {}".형식(f.세부 정보) 로그.오류(오류) 반환 오류 반환 세션 |
가상 머신 목록
|
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 |
def list_vms(세션): vm_count = 0 vms = 세션.xenapi.VM.get_all() 로그.정보("서버에 {} VM 개체가 있습니다(템플릿 포함):".형식(len(vms))) 로그.정보("-----------------------------------------------------------") 로그.정보("S.번호,VM이름,전원 상태,Vcpus,최대 메모리,네트워크 정보,설명") 로그.정보("-----------------------------------------------------------") vm_details = [] 에 대한 vm in vms: 네트워크 정보 = 'N/A' 기록 = 세션.xenapi.VM.get_record(vm) 만약 not (기록["is_a_template"]) 그리고 not (기록["is_control_domain"]): 로그.debug(기록) vm_count = vm_count + 1 이름 = 기록["name_label"] 이름_설명 = 기록["이름_설명"] power_state = 기록["power_state"] vcpus = 기록["VCPUs_max"] memory_static_max = 기록["memory_static_max"] 만약 기록["power_state"] != '중단됨': ip_ref = 세션.xenapi.VM_guest_metrics.get_record(기록['guest_metrics']) 네트워크 정보 = ','.join([str(elem) 에 대한 elem in ip_ref['networks'].값()]) else: 계속 실행 중인 VM만 나열하는 # vm_info = {'name': 이름, 'power_state': power_state, 'vcpus': vcpus, 'memory_static_max': memory_static_max, 'networkinfo': 네트워크 정보, '이름_설명': 이름_설명} vm_details.추가(vm_info) 로그.정보(vm_info) 로그.정보("서버에 {} VM 개체와 {} 템플릿이 있습니다.".형식(vm_count, len(vms) - vm_count)) 로그.debug(vm_details) 반환 vm_details |
가상 머신 생성
|
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 |
def create_vm(세션, os_name, 템플릿, new_vm_name, cpus="default", 최대 메모리="default", 만료_분=최대_만료_분): 오류 = '' vm_os_name = '' vm_ip_addr = '' prov_start_time = 시간.시간() 시도: 로그.정보("\n--- VM을 만드는 중입니다: " + new_vm_name + " 사용 " + 템플릿) pifs = 세션.xenapi.PIF.get_all_records() 최저 = 없음 에 대한 pifRef in pifs.키(): 만약 (최저 는 없음) 또는 (pifs[pifRef]['device'] < pifs[최저]['device']): 최저 = pifRef 로그.debug("장치에서 PIF 선택 중입니다: {}".형식(pifs[최저]['device'])) ref = 최저 mac = pifs[ref]['MAC'] 장치 = pifs[ref]['device'] 모드 = pifs[ref]['ip_configuration_mode'] IP_ADDR = pifs[ref]['IP'] net_mask = pifs[ref]['IP'] 게이트웨이 = pifs[ref]['게이트웨이'] dns_서버 = pifs[ref]['DNS'] 로그.debug("{},{},{},{},{},{},{}".형식(mac, 장치, 모드, IP_ADDR, net_mask, 게이트웨이, dns_서버)) # 모든 VM 개체 나열 vms = 세션.xenapi.VM.get_all_records() 로그.debug("서버에 {} VM 개체가 있습니다(템플릿 포함).".형식(len(vms))) 템플릿 = [] 모든_템플릿 = [] 에 대한 vm in vms: 기록 = vms[vm] res_type = "VM" 만약 기록["is_a_template"]: res_type = "템플릿" 모든_템플릿.추가(vm) # 주어진 템플릿 찾기 만약 기록["name_label"].시작(템플릿): 템플릿.추가(vm) 로그.debug(" name_label = %s인 %8을 찾았습니다." % (res_type, 기록["name_label"])) 로그.debug("서버에 {} 템플릿 및 {} VM 개체가 있습니다.".형식(len(모든_템플릿), len(vms) - len( 모든_템플릿))) 로그.debug("복제할 {} 템플릿 선택 중".형식(템플릿)) 만약 not 템플릿: 로그.오류("{} 템플릿을 찾을 수 없습니다. 종료합니다.".형식(템플릿)) sys.exit(1) template_ref = 템플릿[0] 로그.debug(" 선택한 템플릿입니다: {}".형식(세션.xenapi.VM.get_name_label(template_ref))) 169.x 주소 수신 시 # 재시도 IPADDR_MAX_RETRIES = 3 retry_count = 1 is_local_ip = True vm_ip_addr = "" 동안 is_local_ip 그리고 retry_count != IPADDR_MAX_RETRIES: 로그.정보("템플릿에서 새 VM 설치 중 - #{} 시도".형식(retry_count)) vm = 세션.xenapi.VM.복제(template_ref, new_vm_name) 네트워크 = 세션.xenapi.PIF.get_network(최저) 로그.debug("선택한 PIF가 네트워크에 연결되었습니다: {}".형식( 세션.xenapi.네트워크.get_name_label(네트워크))) vif = 세션.xenapi.VIF.get_all() 로그.debug(("VIF 수=" + str(len(vif)))) 에 대한 i in 범위(len(vif)): vmref = 세션.xenapi.VIF.get_VM(vif[i]) a_vm_name = 세션.xenapi.VM.get_name_label(vmref) 로그.debug(str(i) + "." + 세션.xenapi.네트워크.get_name_label( 세션.xenapi.VIF.get_network(vif[i])) + " " + a_vm_name) 만약 a_vm_name == new_vm_name: 세션.xenapi.VIF.이동(vif[i], 네트워크) 로그.debug("커널 명령줄에 비대화형 추가하기") 세션.xenapi.VM.set_PV_args(vm, "비대화형") 로그.debug("VM의 디스크를 인스턴스화할 SR 선택") pool = 세션.xenapi.pool.get_all()[0] default_sr = 세션.xenapi.pool.get_default_SR(pool) default_sr = 세션.xenapi.SR.get_record(default_sr) 로그.debug("SR 선택: {}(uuid {})".형식(default_sr['name_label'], default_sr['uuid'])) 로그.debug("템플릿 사양에서 서버에 스토리지 프로비저닝 요청하기") 설명 = new_vm_name + " 에서 " + 템플릿 + " 켜기 " + str(날짜 시간.날짜 시간.utcnow()) 세션.xenapi.VM.설정_이름_설명(vm, 설명) 만약 cpus != "default": 로그.정보("cpus를 " + cpus) 세션.xenapi.VM.set_VCPUs_max(vm, int(cpus)) 세션.xenapi.VM.set_VCPUs_at_startup(vm, int(cpus)) 만약 최대 메모리 != "default": 로그.정보("메모리를 " + 최대 메모리) 세션.xenapi.VM.set_memory(vm, 최대 메모리) # 8GB="8589934592" 또는 4GB="4294967296" 세션.xenapi.VM.프로비저닝(vm) 로그.정보("VM 시작") 세션.xenapi.VM.시작(vm, False, True) 로그.debug(" VM 부팅 중") 로그.debug("설치가 완료되기를 기다리는 중") # OS 이름 및 IP 가져오기 로그.정보("OS 이름 및 IP 가져오기...") 구성 = read_config() vm_network_timeout_secs = int(구성.get("common", "vm.network.timeout.secs")) 만약 vm_network_timeout_secs > 0: TIMEOUT_SECS = vm_network_timeout_secs 로그.정보("VM OS 주소의 최대 대기 시간(초)은 {0}입니다.".형식(str(TIMEOUT_SECS))) 만약 "win" not in 템플릿: 최대 시간 = 시간.시간() + TIMEOUT_SECS 동안 READ_OS_NAME(세션, vm) 는 없음 그리고 시간.시간() < 최대 시간: 시간.수면(1) vm_os_name = READ_OS_NAME(세션, vm) 로그.정보("VM OS 이름: {}".형식(vm_os_name)) else: # 미정: Windows VM에서 네트워크가 새로 고쳐질 때까지 기다립니다. 시간.수면(60) 로그.정보("IP 주소의 최대 대기 시간(초)은 " + str(TIMEOUT_SECS)) 최대 시간 = 시간.시간() + 타임아웃_SECS # IP가 없음 또는 169.xx(사용 가능한 IP가 없는 경우 기본값)가 아닐 때까지 기다린 후 시간 초과합니다. #에 연결할 수 없습니다. 동안 (READ_IP_ADDRESS(세션, vm) 는 없음 또는 READ_IP_ADDRESS(세션, vm).시작( '169')) 그리고 \ 시간.시간() < 최대 시간: 시간.수면(1) vm_ip_addr = READ_IP_ADDRESS(세션, vm) 로그.정보("VM IP: {}".형식(vm_ip_addr)) 만약 vm_ip_addr.시작('169'): 로그.정보("사용할 수 있는 네트워크 IP가 없습니다. 이 VM을 삭제하는 중 ... ") 기록 = 세션.xenapi.VM.get_record(vm) power_state = 기록["power_state"] 만약 power_state != '중단됨': 세션.xenapi.VM.하드_셧다운(vm) 삭제_모든_디스크(세션, vm) 세션.xenapi.VM.파괴(vm) 시간.수면(5) is_local_ip = True retry_count += 1 else: is_local_ip = False 로그.정보("최종 VM IP: {}".형식(vm_ip_addr)) |
VM 삭제
|
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 |
def delete_vm(세션, vm_name): 로그.정보("VM 삭제 중입니다: " + vm_name) 삭제_시작_시간 = 시간.시간() vm = 세션.xenapi.VM.get_by_name_label(vm_name) 로그.정보("이름으로 검색된 VM 수 - " + vm_name + " : " + str(len(vm))) 에 대한 j in 범위(len(vm)): 기록 = 세션.xenapi.VM.get_record(vm[j]) power_state = 기록["power_state"] 만약 power_state != '중단됨': # session.xenapi.VM.shutdown(vm[j]) 세션.xenapi.VM.하드_셧다운(vm[j]) # print_all_disks(세션, vm[j]) 삭제_모든_디스크(세션, vm[j]) 세션.xenapi.VM.파괴(vm[j]) delete_end_time = 시간.시간() delete_duration = 라운드(delete_end_time - 삭제_시작_시간) # CB에서 삭제 uuid = 기록["uuid"] doc_key = uuid cbdoc = CBDoc() doc_result = cbdoc.get_doc(doc_key) 만약 doc_result: doc_value = doc_result.값 doc_value["state"] = '삭제됨' 현재_시간 = 시간.시간() doc_value["삭제된 시간"] = 현재_시간 만약 doc_value["created_time"]: doc_value["live_duration_secs"] = 라운드(현재_시간 - doc_value["created_time"]) doc_value["삭제_기간_초"] = delete_duration cbdoc.save_dynvm_doc(doc_key, doc_value) |
가상 머신 사용 내역
다른 유용한 데이터와 함께 생성 및 종료된 모든 VM의 기록을 유지하는 것이 좋습니다. 다음은 Couchbase에 저장된 json 문서의 예입니다. 무료 Nosql 데이터베이스 서버에 추가합니다. 새 VM이 프로비저닝될 때마다 키를 xen opac 참조 UUID로 사용하여 새 문서를 삽입하고 VM이 종료될 때마다 동일하게 업데이트합니다. VM의 실시간 사용 시간과 각 사용자가 프로비저닝/종료한 방법을 추적합니다.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
기록 = 세션.xenapi.VM.get_record(vm) uuid = 기록["uuid"] # CB에 문서로 저장 상태 = "사용 가능" 사용자 이름 = new_vm_name pool = "dynamicpool" doc_value = {"ipaddr": vm_ip_addr, "origin": xen_host_description, "os": os_name, "state": 상태, "poolId": pool, "prevUser": "", "username": 사용자 이름, "ver": "12", "memory": memory_static_max, "os_version": vm_os_name, "name": new_vm_name, "created_time": prov_end_time, "create_duration_secs": create_duration, "cpu": vcpus, "디스크": 디스크_정보} # doc_value["mac_address"] = mac_address doc_key = uuid cb_doc = CBDoc() cb_doc.save_dynvm_doc(doc_key, doc_value) |
|
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 |
클래스 CBDoc: def __init__(self): 구성 = read_config() self.cb_server = 구성.get("couchbase", "couchbase.server") self.cb_bucket = 구성.get("couchbase", "couchbase.bucket") self.cb_username = 구성.get("couchbase", "couchbase.username") self.cb_userpassword = 구성.get("couchbase", "couchbase.userpassword") 시도: self.cb_cluster = 클러스터('couchbase://' + self.cb_server) self.cb_auth = 비밀번호 인증기(self.cb_username, self.cb_userpassword) self.cb_cluster.인증(self.cb_auth) self.cb = self.cb_cluster.open_bucket(self.cb_bucket) 예외 예외 as e: 로그.오류('연결 실패: %s ' % self.cb_server) 로그.오류(e) def get_doc(self, doc_key): 시도: 반환 self.cb.get(doc_key) 예외 예외 as e: 로그.오류('문서 %s를 가져오는 동안 오류가 발생했습니다!' % doc_key) 로그.오류(e) def save_dynvm_doc(self, doc_key, doc_value): 시도: 로그.정보(doc_value) self.cb.업서트(doc_key, doc_value) 로그.정보("%s 추가/업데이트 성공" % doc_key) 예외 예외 as e: 로그.오류('키가 있는 문서: %s 저장 오류' % doc_key) 로그.오류(e) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
"dynserver-pool": { "cpu": "6", "create_duration_secs": 65, "created_time": 1583518463.8943903, "삭제_기간_초": 5, "삭제된 시간": 1583520211.8498628, "디스크": "75161927680", "ipaddr": "x.x.x.x", "live_duration_secs": 1748, "memory": "6442450944", "name": "Win2019-Server-1node-DynVM", "origin": "s827", "os": "win16", "os_version": "", "poolId": "dynamicpool", "prevUser": "", "state": "삭제됨", "username": "Win2019-Server-1node-DynVM", "ver": "12" } |
구성
카우치베이스 서버, xenhost 서버, 템플릿 세부 정보, 기본 만료 및 네트워크 시간 초과 값과 같은 동적 VM 서버 관리자 서비스 구성은 간단한 .ini 형식으로 유지 관리할 수 있습니다. 새 Xen 호스트가 수신되면 별도의 섹션으로 추가하기만 하면 동적 VM SM 서비스를 다시 시작하지 않고도 구성이 동적으로 로드됩니다.
샘플 구성 파일: .dynvmservice.ini
|
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 |
[카우치베이스] 카우치베이스.서버=<카우치베이스-hostIp> 카우치베이스.버킷=<버킷-이름> 카우치베이스.사용자 이름=<사용자 이름> 카우치베이스.사용자 비밀번호=<비밀번호> [공통] vm.만료.분=720 vm.네트워크.시간 초과.초=400 [xenhost1] 호스트.이름=<xenhostip1> 호스트.사용자=root 호스트.비밀번호=xxxx 호스트.스토리지.이름=로컬 스토리지 01 센토스.템플릿=tmpl-cnt7.7 centos7.템플릿=tmpl-cnt7.7 창.템플릿=tmpl-win16dc - 패치 적용 (1) centos8.템플릿=tmpl-cnt8.0 oel8.템플릿=tmpl-oel8 deb10.템플릿=tmpl-deb10-too debian10.템플릿=tmpl-deb10-too 우분투18.템플릿=tmpl-ubu18-4cgb suse15.템플릿=tmpl-suse15 [xenhost2] 호스트.이름=<xenhostip2> 호스트.사용자=root 호스트.비밀번호=xxxx 호스트.스토리지.이름=ssd 센토스.템플릿=tmpl-cnt7.7 창.템플릿=tmpl-win16dc - 패치 적용 (1) centos8.템플릿=tmpl-cnt8.0 oel8.템플릿=tmpl-oel8 deb10.템플릿=tmpl-deb10-too debian10.템플릿=tmpl-deb10-too 우분투18.템플릿=tmpl-ubu18-4cgb suse15.템플릿=tmpl-suse15 [xenhost3] ... [xenhost4] ... |
예제
curl을 사용한 REST API 호출 샘플
|
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 |
$ curl 'http://127.0.0.1:5000/showall' { "1": [ { "memory_static_max": "6442450944", "name": "tunable-rebalance-out-Apr-29-13:43:59-7.0.0-19021", "이름_설명": "튜너블-리밸런스-아웃-Apr-29-13:43:59-7.0.0-19021에서 tmpl-win16dc - 패치됨 (1) on 2020-04-29 20:43:33.785778", "네트워크 정보": "172.23.137.20,172.23.137.20,fe80:0000:0000:0000:0585:c6e8:52f9:91d1", "power_state": "Running", "vcpus": "6" }, { "memory_static_max": "6442450944", "name": "nserv-nserv-rebalanceinout_P0_Set1_compression-Apr-29-06:36:12-7.0.0-19022", "이름_설명": "nserv-nserv-rebalanceinout_P0_Set1_compression-Apr-29-06:36:12-7.0.0-19022 from tmpl-win16dc - PATCHED (1 on 2020-04-29 13:36:53.717776)", "네트워크 정보": "172.23.136.142,172.23.136.142,fe80:0000:0000:0000:744d:fd63:1a88:2fa8", "power_state": "Running", "vcpus": "6" } ], "2": [ .. ] "3": [ .. ] "4": [ .. ] "5": [ .. ] "6": [ .. ] } $ curl 'http://127.0.0.1:5000/getavailablecount/windows' 2 $ curl 'http://127.0.0.1:5000/getavailablecount/centos' 10 $ curl 'http://127.0.0.1:5000/getservers/demoserver?os=centos&count=3' ["172.23.137.73", "172.23.137.74", "172.23.137.75"] $ curl 'http://127.0.0.1:5000/getavailablecount/centos' 7 $ curl 'http://127.0.0.1:5000/releaseservers/demoserver?os=centos&count=3' [ "demoserver1", "demoserver2", "demoserver3" ] $ curl 'http://127.0.0.1:5000/getavailablecount/centos' 10 $ |
단일 VM을 사용한 Jenkins 작업
|
1 2 3 4 5 6 7 8 |
curl -s -o ${BUILD_TAG}_vm.json "http:///getservers/${BUILD_TAG}?os=windows&format=detailed" VM_IP_ADDRESS="`cat ${BUILD_TAG}_vm.json |egrep ${BUILD_TAG}|cut -f2 -d':'|xargs`" 만약 [[ $VM_IP_ADDRESS =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; 다음 echo 유효 IP 받은 else echo NOT a 유효 IP 받은 exit 1 fi |
여러 VM이 필요한 Jenkins 작업
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
curl -s -o ${BUILD_TAG}.json "${SERVER_API_URL}/getservers/${BUILD_TAG}?os=${OS}&count=${COUNT}&format=detailed" cat ${BUILD_TAG}.json VM_IP_ADDRESS="`cat ${BUILD_TAG}.json |egrep ${BUILD_TAG}|cut -f2 -d':'|xargs|sed 's/,//g'`" echo $VM_IP_ADDRESS ADDR=() INDEX=1 에 대한 IP in `echo $VM_IP_ADDRESS` do 만약 [[ $IP =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; 다음 echo 유효 IP=$IP 받은 ADDR[$INDEX]=${IP} INDEX=`expr ${INDEX} + 1` else echo NOT a 유효 IP=$IP 받은 exit 1 fi 완료 echo ${ADDR[1]} echo ${ADDR[2]} echo ${ADDR[3]} echo ${ADDR[4]} |
주요 고려 사항
다음은 이 과정에서 제가 관찰한 몇 가지 사항이며, 보다 안정적으로 처리하는 것이 좋습니다.
- 서로 다른 Xen 호스트 간에 서로 다른 스토리지 이름/ID 처리
- 서비스 입력 구성 파일에서 VM 스토리지 장치 이름을 추적합니다.
- 프로비저닝하는 동안 일부 Xen 호스트에서만 사용할 수 있는 부분 템플릿 처리
- 네트워크 IP를 사용할 수 없고 Xen API가 Windows에서 기본 169.254.xx.yy를 가져오는 경우. 169가 아닌 주소를 가져오거나 시간이 초과될 때까지 기다립니다.
- 일부 템플릿이 없을 수 있으므로 릴리스 서버는 OS 템플릿을 무시해야 합니다. Xen 호스트
- 특정 Xen 호스트 참조에 대한 프로비저닝
- 사용 가능한 IP 없음 처리 또는 n생성된 일부 가상 머신에 대한 네트워크 IP를 얻지 못했습니다.
- P랜을 사용하여 동적 VM 대상 Xen 호스트에 대해 다른 서브넷을 사용하도록 설정할 수 있습니다. 기본 네트워크 DHCP IP 임대 만료는 일 단위(예: 7일)로 지정되며 새 IP는 제공되지 않습니다.
- 용량 확인을 처리하여 진행 중인 IP를 예약된 IP로 계산하고 현재 가득 찬 것보다 적은 수를 표시해야 합니다. 그렇지 않으면 진행 중인 요청과 수신 요청 모두에 문제가 있을 수 있습니다. 병렬 요청을 생성하고 확인하는 동안 하나 또는 두 개의 VM(cpus/메모리/디스크 크기)이 버퍼에 있을 수 있습니다.
참조
동적 VM 서버 관리자 서비스를 생성하는 데 도움이 되는 몇 가지 주요 참고 자료입니다.
- https://www.couchbase.com/downloads
- https://wiki.xenproject.org/wiki/XAPI_Command_Line_Interface
- https://xapi-project.github.io/xen-api/
- https://docs.citrix.com/en-us/citrix-hypervisor/command-line-interface.html
- https://github.com/xapi-project/xen-api-sdk/tree/master/python/samples
- https://www.citrix.com/community/citrix-developer/citrix-hypervisor-developer/citrix-hypervisor-developing-products/citrix-hypervisor-staticip.html
- https://docs.ansible.com/ansible/latest/modules/xenserver_guest_module.html
- https://github.com/terra-farm/terraform-provider-xenserver
- https://github.com/xapi-project/xen-api/blob/master/scripts/examples/python/renameif.py
- https://xen-orchestra.com/forum/topic/191/single-device-not-reporting-ip-on-dashboard/14
- https://xen-orchestra.com/blog/xen-orchestra-from-the-cli/
- https://support.citrix.com/article/CTX235403
즐거운 독서 시간이 되셨기를 바랍니다!
면책 조항: Xen 호스트를 다루는 경우 이 내용을 참고하시기 바랍니다. 도움이 될 만한 새로운 정보가 있으면 언제든지 공유해 주세요. 긍정적인 피드백을 보내주시면 감사하겠습니다!
이 과정에서 도움을 준 라주 수라바르잘라, 리탐 샤르마, 웨인 시우, 톰 트러시, 제임스 리에게 감사의 인사를 전합니다.