GitHub를 통한 분산 서버 관리

GitHub 리포지토리에서 파일을 업데이트하는 샘플 Python 코드

C05348A3-9AB8-42C9-A6E0-81DB3AC59FEB
           

이 스크립트를 사용하여 수많은 서버에서 실행 중인 관리 스크립트 세트를 업데이트했습니다. "git pull"로 스크립트를 체크아웃할 수도 있었지만 어떤 스크립트가 어떤 서버에 설치되었는지에 대한 추가 제어가 필요했기 때문에 이를 수행하는 Python 스크립트를 작성했습니다.

저는 이것을 분산형 또는 분산형 접근 방식이라고 부릅니다. Salt, Chef 또는 Puppet 마스터 서버와 같은 중앙 기관이 모든 서버에 새 관리 스크립트를 푸시하는 것과 달리 서버가 GitHub에서 업데이트된 스크립트를 가져올 수 있기 때문입니다.

스크립트는 PyGitHub 라이브러리 를 사용하므로 Yaml 라이브러리와 함께 설치부터 시작하겠습니다.

pip3 PyGithub 설치
 pip3 pyaml 설치

GitHub에서 API 토큰 얻기

다음 스크립트를 사용하려면 먼저 GitHub에서 API 토큰을 가져와야 합니다. GitHub 계정에서 "설정" 및 "개발자 설정"으로 이동한 다음 "개인 액세스 토큰"을 클릭하고 새 토큰을 만듭니다. 지금은 추가 권한이 필요하지 않습니다.

특정 저장소뿐만 아니라 계정에 대한 액세스를 허용하는 개인 액세스 토큰을 반드시 보호해야 합니다. 이를 위해 RSA-crypto 라이브러리 를 사용하여 암호화하기로 결정했습니다. 다음 코드는 라이브러리를 설치하고 새 RSA 키 세트를 만들고 구성 파일의 개인 액세스 토큰을 ghe_token 이라는 옵션 값으로 암호화합니다. 해당 라이브러리 및 관련 명령줄 도구 사용에 대한 자세한 내용은 공개/개인 키 RSA 암호화Python 암호화 를 참조하십시오.

pip3 rsa-crypto 설치

 rsa_crypto 생성
 키 비밀번호 입력:
 키 비밀번호 재입력:
 키 생성 중...
 생성된 암호로 보호된 개인/공개 키 파일 /Users/me/rsa_key.bin
 "extract" 키워드를 사용하여 공개 및 개인 키 파일을 작성하십시오.

 터치 ~/.rsa_values.conf

 rsa_crypto 세트 -o ghe_token
 키 사용: /Users/me/rsa_key.bin
 암호화된 키 열기 /Users/me/rsa_key.bin
 키 비밀번호 입력:
 값 입력:
 섹션 설정: 옵션:ghe_token
 업데이트된 /Users/me/.rsa_values.conf

그런 다음 Python 스크립트는 개인 액세스 토큰을 검색하고 사용하기 위해 ghe_token 구성 "옵션"을 읽고 해독하도록 구성됩니다. 이것이 너무 복잡해 보이는 경우 다른 솔루션을 자유롭게 찾을 수 있지만 액세스 토큰을 스크립트에 직접 포함하지 마십시오. GitHub에서 누구나 보고 사용할 수 있게 될 가능성이 높습니다!

파이썬 스크립트

이 스크립트를 사용하려면 몇 가지 매개변수를 변경하고 ghe_repo 를 변경하여 자신의 저장소를 가리키도록 하고 local_base_path 를 변경하여 서버 관리 스크립트가 상주할 시스템의 로컬 디렉토리를 가리켜야 합니다.

이 스크립트는 공개 GitHub를 사용하고 있다고 가정하지만 조직 내부의 비공개 GitHub Enterprise 리포지토리를 사용하는 경우 필요한 코드도 포함되어 주석 처리되었습니다.

GitHub Repository 에서 이 스크립트를 찾을 수 있습니다.

스크립트는 먼저 구성 파일 update_scripts.yaml을 검색합니다. 이 파일에는 세 개의 섹션이 있습니다.

  • update_always - GitHub에서 항상 가져와야 하는 파일 목록을 포함합니다.
  • update_if_present - 존재하는 경우에만 GitHub에서 업데이트해야 하는 파일 목록을 포함합니다.
  • remove - 로컬 서버에서 삭제해야 하는 파일 목록을 포함합니다.

이 파일을 사용하면 모든 서버에 배포하려는 스크립트를 중앙에서 제어할 수 있습니다. 새 스크립트를 설치해야 하는 경우 GitHub에 푸시하고 구성 파일에 항목을 추가하면 이 스크립트가 실행될 때 서버에서 새 스크립트를 다운로드합니다. 또한 touch 명령을 사용하여 파일을 만든 다음 이 업데이트 스크립트를 실행하여 update_if_present 섹션에서 스크립트를 다운로드하도록 시스템을 "속일 수" 있습니다. 스크립트가 목록에 포함되어 있으면 스크립트도 자동으로 업데이트됩니다.

파일의 세 섹션이 모두 처리되고, update_alwaysupdate_if_present 섹션의 파일은 GitHub 해시에서만 업데이트되므로 가져오기가 더 효율적입니다. 스크립트는 또한 현재 사용자에 대해 모든 python 스크립트를 실행 가능하게 만듭니다.

#!/usr/bin/env 파이썬
 # -*- 코딩: utf-8 -*-
 from __future__ import(absolute_import, division,
 print_function, unicode_literals)

 __author__ = "Videre Research, LLC"
 __버전__ = "1.0.2"


 '''

 서버 관리 스크립트 및 기타 지원 스크립트를 설치하고 구성합니다.

 '''


 # 수입품 ################################################# ###############

 수입 OS
 수입 시스템
 가져오기 로깅
 역추적 가져오기
 수입 base64
 가져오기 깃허브
 yaml 가져오기
 해시립 가져오기
 rsa_crypto 가져오기
 가져오기 argparse

 if (sys.version_info > (3, 0)):
 urllib.parse에서 urljoin 가져오기
 또 다른:
 urlparse에서 urljoin 가져오기


 # 글로벌 ################################################# ###############

 # # 자체 내부 GitHub 리포지토리를 사용하는 경우 주석 처리를 제거합니다.
 # ghe_organization = 'my_ghe_repo'
 # ghe_hostname = mydomain.com

 ghe_repo = '크리스토프 게이지/GitHub'
 remote_base_path = '/'
 local_base_path = '/opt/스크립트'
 remote_config_file = '업데이트_스크립트.yaml'
 ghe_branch = '메인'

 로거 = logging.getLogger()
 logging.basicConfig(레벨=logging.INFO)
 logger.info("경로: %s" % (os.path.realpath(__file__)))
 logger.info("버전: %s" % (__version__))

 인수 = argparse.Namespace(옵션='ghe_token')
 ghe_token = rsa_crypto.decrypt_value(인수)


 # 코드 ################################################# ######################

 def get_ghe(remote_file, repo, always_update):
 """GHE에서 파일을 가져와 로컬에 저장"""
 my_file_name = os.path.basename(원격 파일)
 remote_file_name = urljoin(원격_베이스_경로, 원격_파일)
 local_file_name = os.path.join(local_base_path, my_file_name)
 logger.info("원격 GHE 파일 %s%s을(를) %s(으)로 검색 중" % (repo.full_name, remote_file_name, local_file_name))

 노력하다:
 remoteSHA = repo.get_contents(remote_file_name, ref=ghe_branch).sha
 e로 github.UnknownObjectException 제외:
 logger.error(f"원격 파일을 찾을 수 없습니다 {remote_file_name}")
 반품
 예외를 제외하고 e:
 logger.error("오류 {0}".format(str(e)))
 logger.error(traceback.format_exc())
 반품

 # 파일이 존재하는 경우 업데이트가 필요한지 확인하기 위해 해시를 얻습니다.
 os.path.exists(local_file_name):
 # 로컬 파일의 SHA1 해시 계산
 file_for_hash로 open(local_file_name, 'rb') 사용:
 데이터 = file_for_hash.read()
 파일 크기 = len(데이터)
 내용 = "blob" + str(파일 크기) + "\0" + data.decode('utf-8')
 encode_content = content.encode('utf-8')
 localSHA = hashlib.sha1(encoded_content).hexdigest()
 remoteSHA == localSHA인 경우:
 logger.info('파일이 존재하고 해시가 동일하며 업데이트가 아닌 최신 파일이 이미 있습니다.')
 반품
 또 다른:
 logger.info('파일이 존재하고 해시가 다릅니다 %s - %s' % (remoteSHA, localSHA))
 또 다른:
 # 이 플래그는 파일이 이미 존재하는 경우에만 업데이트되어야 함을 나타냅니다.
 always_update가 아닌 경우:
 logger.info('파일이 업데이트되지 않고 존재하지 않습니다')
 반품
 노력하다:
 file_contents = repo.get_contents(remote_file_name, ref=ghe_branch)
 local_file_content = str(base64.b64decode(file_contents.content).decode('utf-8', '무시'))

 # 디스크에 새 파일 쓰기
 open(local_file_name, "w")을 text_file로 사용:
 text_file.write(local_file_content)
 if my_file_name.endswith('.py'):
 os.chmod(로컬_파일_이름, 0o700)
 또 다른:
 os.chmod(로컬_파일_이름, 0o400)
 logger.info('파일이 업데이트되었습니다')

 예외를 제외하고 e:
 logger.error("오류 {0}".format(str(e)))
 logger.error(traceback.format_exc())

 def 메인():
 """주요 기능."""

 gh = github.Github(login_or_token=ghe_token)
 repo = gh.get_repo(ghe_repo)

 # # 자체 내부 GitHub 리포지토리를 사용하는 경우 주석 처리를 제거합니다.
 # gh = github.Github(base_url=f"https://{ghe_hostname}/api/v3", login_or_token=ghe_token)
 # 조직 = gh.get_organization(ghe_organization)
 # repo = org.get_repo(ghe_repo)

 os.path.exists(local_base_path)가 아닌 경우:
 os.makedirs(local_base_path)

 remote_file_name = urljoin(remote_base_path, remote_config_file)
 logger.info("원격 GHE 파일 %s%s 검색 중" % (repo.full_name, remote_file_name))

 노력하다:
 file_contents = repo.get_contents(remote_file_name, ref=ghe_branch)
 text_contents = str(base64.b64decode(file_contents.content).decode('utf-8', '무시'))
 file_list = yaml.load(text_contents, Loader=yaml.SafeLoader)
 logger.info(yaml.safe_dump(file_list, default_flow_style=False))
 예외를 제외하고 e:
 e.args[0] == 404인 경우:
 logger.error(f"원격 파일을 찾을 수 없습니다 {remote_file_name}")
 sys.exit(1)
 또 다른:
 logger.error("오류 {0}".format(str(e)))
 logger.error(traceback.format_exc())
 sys.exit(1)

 file_list['update_always']의 파일:
 logger.info(파일)
 get_ghe(파일, 저장소, True)

 file_list['update_if_present']의 파일:
 logger.info(파일)
 get_ghe(파일, 저장소, False)

 file_list['remove']의 파일:
 os.path.exists(파일):
 os.remove(파일)
 logger.info('파일 %s이(가) 삭제되었습니다' % 파일)

 sys.exit(0)

 #################################################### ###############################


 __name__ == "__main__"인 경우:
 기본()

 # ENDOFFILE ################################################# #######
댓글을 게시했습니다: 0

Tagged with:
GitHub