SSH를 사용하여 앱을 인스턴스에 연결

인스턴스에서 서비스 계정을 사용하여 자동화된 작업을 실행하고 다른 Google Cloud Platform API와 상호작용하는 경우 해당 서비스 계정에 다른 Compute Engine 인스턴스에 대한 SSH 액세스가 필요할 수도 있습니다. 이 가이드에서는 SSH 연결을 통해 인스턴스에 액세스하도록 앱을 구성하는 방법을 보여줍니다. 이 가이드의 샘플 앱은 SSH 키 관리에 서비스 계정과 OS 로그인을 사용합니다.

연습 과정을 건너뛰고 전체 코드 샘플을 보려면 GoogleCloudPlatform/python-docs-samples GitHub 페이지를 방문하세요.

목표

이 가이드에서는 다음과 같은 목표를 달성하는 방법을 안내합니다.

  • 인스턴스에 연결할 앱에 대한 OS 로그인 SSH 액세스 권한을 부여하도록 서비스 계정을 만들고 구성합니다.
  • 서비스 계정과 연결된 인스턴스를 만듭니다.
  • 서비스 계정을 사용하여 고유한 SSH 키를 관리하고 SSH 연결을 설정하도록 인스턴스에서 샘플 앱을 구성합니다.
  • 서비스 계정이 연결된 인스턴스에서 앱을 실행합니다.
  • 서비스 계정 키를 직접 제공하고 추가 SSH 매개변수를 지정해야 하는 경우 Compute Engine 외부에서 앱을 실행합니다.

비용

이 가이드에서는 Compute Engine 등 비용이 청구될 수 있는 GCP 구성요소를 사용합니다.

GCP 신규 사용자는 무료 체험판을 이용할 수 있습니다.

시작하기 전에

  1. Google 계정에 로그인합니다.

    아직 계정이 없으면 새 계정을 등록하세요.

  2. Google Cloud Platform 프로젝트를 선택하거나 만듭니다.

    리소스 관리 페이지로 이동

  3. Google Cloud Platform 프로젝트에 결제가 사용 설정되어 있는지 확인하세요.

    결제 사용 설정 방법 알아보기

  4. 사용자 계정에는 여러 Compute Engine 리소스를 만들고 삭제 및 수정할 수 있는 권한이 있어야 합니다. 이 가이드에서는 프로젝트에 대한 다음과 같은 IAM 역할이 있다고 가정합니다.
    • compute.instanceAdmin.v1
    • compute.networkAdmin
    • compute.osAdminLogin
    • iam.serviceAccountAdmin
    • iam.serviceAccountKeyAdmin
    • iam.serviceAccountUser
  5. 이 가이드에서는 Cloud Shell을 사용하여 gcloud 명령어를 실행한다고 가정합니다.

서비스 계정 및 예시 인스턴스 생성 및 구성

이 가이드에서는 앱이 원격 인스턴스에서 SSH 명령어를 실행하는 방법을 보여주기 위해 하나의 서비스 계정과 두 개의 인스턴스를 사용합니다.

다음 명령어를 사용하여 테스트 환경을 구성합니다.

  1. 콘솔에서 Cloud Shell을 엽니다.

    Cloud Shell 열기

  2. 환경 변수를 내보내서 다음 명령어에 대한 프로젝트 ID를 설정합니다.

    export PROJECT_ID='[PROJECT_ID]'
    
  3. 프로젝트에 새 서비스 계정을 만듭니다. 이 예시에서는 이름이 ssh-account인 서비스 계정을 만듭니다.

    gcloud iam service-accounts create ssh-account --project $PROJECT_ID \
       --display-name "ssh-account"
    
  4. 이 예시에서만 사용할 이름이 ssh-example인 임시 네트워크를 만듭니다.

    gcloud compute networks create ssh-example --project $PROJECT_ID
    
  5. ssh-example 네트워크의 인스턴스에 대한 모든 SSH 연결을 허용하는 방화벽 규칙을 만듭니다.

    gcloud compute firewall-rules create ssh-all --project $PROJECT_ID \
       --network ssh-example --allow tcp:22
    
  6. us-central1-f에 이름이 target인 인스턴스를 만듭니다. 이 인스턴스는 서비스 계정이 SSH를 통해 연결되는 원격 인스턴스의 역할을 합니다. 인스턴스에는 프로젝트 수준이나 인스턴스 수준에서 OS 로그인이 사용 설정되어 있어야 합니다. 이 예시에서는 이 특정 인스턴스에서 --metadata 플래그를 사용하여 OS 로그인을 사용 설정하는 방법을 보여줍니다. 이 예시에서는 이 인스턴스가 API 요청을 실행할 필요가 없으므로 --no-service-account--no-scopes 플래그를 포함합니다.

    gcloud compute instances create target --project $PROJECT_ID \
       --zone us-central1-f --network ssh-example \
       --no-service-account --no-scopes \
       --machine-type f1-micro --metadata=enable-oslogin=TRUE
    
  7. 이름이 target인 인스턴스에 특정한 SSH 연결을 설정할 수 있도록 compute.osAdminLogin IAM 역할을 서비스 계정에 부여합니다. compute.osAdminLogin 역할은 서비스 계정에 인스턴스에 대한 수퍼유저 권한도 부여합니다. 이 역할을 프로젝트의 모든 인스턴스에 적용되도록 프로젝트 수준에서 부여할 수 있지만 이 예시에서는 인스턴스 수준에서만 역할을 부여하여 권한을 제한합니다.

    gcloud compute instances add-iam-policy-binding target \
       --project $PROJECT_ID --zone us-central1-f \
       --member serviceAccount:ssh-account@$PROJECT_ID.iam.gserviceaccount.com \
       --role roles/compute.osAdminLogin
    
  8. us-central1-f에 이름이 source인 인스턴스를 만듭니다. 인스턴스를 ssh-account 서비스 계정과 연결합니다. 또한 서비스 계정이 이 인스턴스에서 API 요청을 실행하기 위해 필요한 cloud-platform 범위를 지정합니다.

    gcloud compute instances create source \
       --project $PROJECT_ID --zone us-central1-f \
       --service-account ssh-account@$PROJECT_ID.iam.gserviceaccount.com  \
       --scopes https://www.googleapis.com/auth/cloud-platform \
       --network ssh-example --machine-type f1-micro
    

이제 서비스 계정은 고유한 SSH 키 쌍을 관리할 수 있고 SSH를 사용하여 target 인스턴스에 연결할 수 있습니다. source 인스턴스는 사용자가 만든 ssh-account 서비스 계정과 연결되어 있으므로, Python용 Cloud 클라이언트 라이브러리는 애플리케이션 기본 사용자 인증 정보를 사용하여 서비스 계정으로 인증하고 이전에 서비스 계정에 부여된 역할을 사용할 수 있습니다.

이제 인스턴스 간에 SSH를 통해 연결할 수 있는 앱을 구성하고 실행합니다.

인스턴스에서 SSH 앱 실행

인스턴스에서 실행 중인 앱에 다른 인스턴스에 대한 SSH 액세스 권한이 필요한 경우 서비스 계정에 대한 SSH 키 쌍을 관리하고 SSH 명령어를 프로그래매틱 방식으로 실행할 수 있습니다. 이 예시에서는 다음 프로세스에 따라 샘플 앱을 실행합니다.

  1. gcloud 명령줄 도구를 사용하여 source 인스턴스에 연결합니다.

    gcloud compute ssh source --project $PROJECT_ID --zone us-central1-f
    
  2. source 인스턴스에서 pip 및 Python 클라이언트 라이브러리를 설치합니다.

    my-username@source:~$ sudo apt update && sudo apt install python-pip -y && pip install --upgrade google-api-python-client
    
  3. GoogleCloudPlatform/python-docs-samples에서 service_account_ssh.py 샘플 앱을 다운로드합니다.

    my-username@source:~$ curl -O https://raw.githubusercontent.com/GoogleCloudPlatform/python-docs-samples/master/compute/oslogin/service_account_ssh.py
    
  4. argparse를 사용하여 명령줄의 변수를 승인하는 샘플 앱을 실행합니다. 이 예시에서는 target 인스턴스에 cowsay를 설치하고 실행하도록 앱에 지시합니다. 이 명령어의 경우 프로젝트 ID를 수동으로 추가합니다.

    my-username@source:~$ python service_account_ssh.py \
        --cmd 'sudo apt install cowsay -y && cowsay "It works!"' \
        --project [PROJECT_ID] --zone us-central1-f --instance target
    
    ⋮
    ___________
      It works!
    -----------
           \   ^__^
            \  (oo)\_______
               (__)\       )\/\
                   ||----w |
                   ||     ||
    
    

앱이 올바르게 실행되면 cowsay 앱에서 출력이 수신됩니다. 원하는 모든 명령어를 포함하도록 --cmd 플래그를 수정할 수 있습니다. 또는 service_account_ssh.py를 가져오고 직접 호출하는 고유한 앱을 제작할 수도 있습니다.

source 인스턴스 연결을 해제하고 Cloud Shell로 돌아가려면 exit를 실행합니다.

Compute Engine 외부에서 SSH 앱 실행

앞의 예시에서는 Compute Engine 인스턴스에서 앱을 실행하여 Python용 Cloud 클라이언트 라이브러리가 애플리케이션 기본 사용자 인증 정보를 사용하여 source 인스턴스와 연결된 서비스 계정을 사용할 수 있도록 했습니다. 이 앱을 Compute Engine 인스턴스 외부에서 실행하는 경우에는 서비스 계정 키를 수동으로 제공해야만 클라이언트 라이브러리가 서비스 계정 및 권한에 액세스할 수 있습니다.

  1. 이 가이드 앞부분에서 만든 target 인스턴스에 대한 외부 IP 주소를 가져옵니다. 이 주소는 Console의 인스턴스 페이지에서 찾을 수도 있고 gcloud 명령줄 도구에서 다음 명령어를 실행하여 찾을 수도 있습니다.

    gcloud compute instances describe target \
       --project $PROJECT_ID --zone us-central1-f
    
  2. 이전 예시에서 사용한 ssh-account 서비스 계정의 서비스 계정 키를 만들고 키 파일을 로컬 워크스테이션에 다운로드합니다.

  3. 이 예시를 실행할 시스템에 서비스 계정 키를 복사합니다.

  4. 시스템에서 이 예시를 실행할 터미널을 엽니다.

  5. 서비스 계정 키 .json 파일이 있는 경로를 가리키도록 GOOGLE_APPLICATION_CREDENTIALS 환경 변수를 설정합니다. 키가 Downloads 폴더에 있는 경우 환경 변수를 다음 예시와 같이 설정합니다.

    $ export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/key.json"
    
  6. 이 시스템에 기본 요건을 설치합니다.

    1. Pythonpip를 설치합니다. Debian 기반 시스템에서는 apt를 사용하여 이 단계를 완료할 수 있습니다.

      $ sudo apt update && sudo apt install python python-pip -y
      
    2. pip를 사용하여 Python용 Cloud 클라이언트 라이브러리 설치

      $ pip install --upgrade google-api-python-client
      
  7. 샘플 앱을 다운로드합니다.

    $ curl -O https://raw.githubusercontent.com/GoogleCloudPlatform/python-docs-samples/master/compute/oslogin/service_account_ssh.py
    
  8. 샘플 앱을 실행합니다. 이 앱이 Compute Engine 외부에서 실행되는 경우 메타데이터 서버를 이용할 수 없으므로 서비스 계정 이메일을 수동으로 지정해야 합니다. 이전에 가져온 target 인스턴스에 대한 외부 IP 주소도 지정해야 합니다.

    $ python service_account_ssh.py \
        --cmd 'sudo apt install cowsay -y && cowsay "It works!"' \
        --account ssh-account@[PROJECT_ID].iam.gserviceaccount.com \
        --project [PROJECT_ID] --hostname [TARGET_EXTERNAL_IP]
    
    ⋮
    ___________
      It works!
    -----------
           \   ^__^
            \  (oo)\_______
               (__)\       )\/\
                   ||----w |
                   ||     ||
    
    

앱이 올바르게 실행되면 cowsay 앱에서 출력이 수신됩니다.

샘플 앱 작동 방식

service_account_ssh.py 샘플 앱은 다음 프로세스에 따라 작동합니다.

  1. OS 로그인 API 객체를 초기화합니다.
  2. 서비스 계정 이메일 주소를 직접 제공하지 않으면 앱이 인스턴스 메타데이터를 읽어 인스턴스와 연결된 서비스 계정을 식별합니다. 이 앱을 Compute Engine 외부에서 실행하는 경우 서비스 계정 주소를 직접 입력해야 합니다.
  3. create_ssh_key() 메서드를 호출하여 이 샘플 애플리케이션이 실행되는 인스턴스에서 서비스 계정에 대한 임시 SSH 키를 생성하고 사용자가 지정할 수 있는 만료 시간이 있는 공개 키를 서비스 계정에 추가합니다.
  4. OS 로그인 API에서 getLoginProfile() 메서드를 호출하여 서비스 계정이 사용하는 POSIX 사용자 이름을 가져옵니다.
  5. run_ssh() 메서드를 호출하여 원격 SSH 명령어를 서비스 계정으로 실행합니다.
  6. 원격 SSH 명령어의 응답을 표시합니다.
  7. 임시 SSH 키 파일을 삭제합니다.
  8. OS 로그인은 만료 시간이 지나면 공개 키 파일을 자동으로 삭제합니다.
def main(cmd, project, instance=None, zone=None,
         oslogin=None, account=None, hostname=None):
    """Run a command on a remote system."""

    # Create the OS Login API object.
    oslogin = oslogin or googleapiclient.discovery.build('oslogin', 'v1')

    # Identify the service account ID if it is not already provided.
    account = account or requests.get(
        SERVICE_ACCOUNT_METADATA_URL, headers=HEADERS).text
    if not account.startswith('users/'):
        account = 'users/' + account

    # Create a new SSH key pair and associate it with the service account.
    private_key_file = create_ssh_key(oslogin, account)

    # Using the OS Login API, get the POSIX user name from the login profile
    # for the service account.
    profile = oslogin.users().getLoginProfile(name=account).execute()
    username = profile.get('posixAccounts')[0].get('username')

    # Create the hostname of the target instance using the instance name,
    # the zone where the instance is located, and the project that owns the
    # instance.
    hostname = hostname or '{instance}.{zone}.c.{project}.internal'.format(
        instance=instance, zone=zone, project=project)

    # Run a command on the remote instance over SSH.
    result = run_ssh(cmd, private_key_file, username, hostname)

    # Print the command line output from the remote instance.
    # Use .rstrip() rather than end='' for Python 2 compatability.
    for line in result:
        print(line.decode('utf-8').rstrip('\n\r'))

    # Shred the private key and delete the pair.
    execute(['shred', private_key_file])
    execute(['rm', private_key_file])
    execute(['rm', private_key_file + '.pub'])

if __name__ == '__main__':

    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument(
        '--cmd', default='uname -a',
        help='The command to run on the remote instance.')
    parser.add_argument(
        '--project',
        help='Your Google Cloud project ID.')
    parser.add_argument(
        '--zone',
        help='The zone where the target instance is locted.')
    parser.add_argument(
        '--instance',
        help='The target instance for the ssh command.')
    parser.add_argument(
        '--account',
        help='The service account email.')
    parser.add_argument(
        '--hostname',
        help='The external IP address or hostname for the target instance.')
    args = parser.parse_args()

    main(args.cmd, args.project, instance=args.instance, zone=args.zone,
         account=args.account, hostname=args.hostname)

create_ssh_key() 메서드는 새로운 SSH 키 쌍을 생성합니다. 그런 다음 이 메서드는 OS 로그인 API에서 users().importSshPublicKey()를 호출하여 공개 키를 서비스 계정과 연결합니다. 또한 users().importSshPublicKey() 메서드는 공개 키가 유효한 상태로 유지되는 기간을 나타내는 만료 값도 허용합니다.

def create_ssh_key(oslogin, account, private_key_file=None, expire_time=300):
    """Generate an SSH key pair and apply it to the specified account."""
    private_key_file = private_key_file or '/tmp/key-' + str(uuid.uuid4())
    execute(['ssh-keygen', '-t', 'rsa', '-N', '', '-f', private_key_file])

    with open(private_key_file + '.pub', 'r') as original:
        public_key = original.read().strip()

    # Expiration time is in microseconds.
    expiration = int((time.time() + expire_time) * 1000000)

    body = {
        'key': public_key,
        'expirationTimeUsec': expiration,
    }
    oslogin.users().importSshPublicKey(parent=account, body=body).execute()
    return private_key_file

권장사항은 새 키 쌍을 정기적으로 생성하도록 서비스 계정을 구성하는 것입니다. 이 예시에서 서비스 계정은 설정하는 각 SSH 연결마다 새 키 쌍을 생성하지만 앱의 요구사항에 더 부합하는 일정으로 실행되도록 수정할 수 있습니다.

users().importSshPublicKey()에 대한 요청 본문에는 키를 언제 만료시켜야 하는지 OS 로그인에 지시하는 expirationTimeUsec 값이 포함되어 있습니다. 계정마다 최대 32KB의 SSH 키 데이터만 있을 수 있으므로 서비스 계정이 작업을 완료한 후에 바로 만료되도록 공개 SSH 키를 구성하는 것이 좋습니다.

서비스 계정이 SSH 키를 구성한 후에는 원격 명령어를 실행할 수 있습니다. 이 예시에서는 앱이 run_ssh() 메서드를 사용하여 원격 인스턴스에서 명령어를 실행하고 명령어 결과를 반환합니다.

def run_ssh(cmd, private_key_file, username, hostname):
    """Run a command on a remote system."""
    ssh_command = [
        'ssh', '-i', private_key_file, '-o', 'StrictHostKeyChecking=no',
        '{username}@{hostname}'.format(username=username, hostname=hostname),
        cmd,
    ]
    ssh = subprocess.Popen(
        ssh_command, shell=False, stdout=subprocess.PIPE,
        stderr=subprocess.PIPE)
    result = ssh.stdout.readlines()
    return result if result else ssh.stderr.readlines()

삭제

이 가이드에서 사용한 리소스 비용이 Google Cloud Platform 계정에 청구되지 않도록 하는 방법은 다음과 같습니다.

다음 명령어를 사용하여 테스트 환경에서 리소스를 삭제합니다.

  1. 콘솔에서 Cloud Shell을 엽니다.

    Cloud Shell 열기

  2. 이름이 source인 인스턴스를 삭제합니다.

    gcloud compute instances delete source \
       --project $PROJECT_ID --zone us-central1-f
    
  3. 이름이 target인 인스턴스를 삭제합니다.

    gcloud compute instances delete target \
       --project $PROJECT_ID --zone us-central1-f
    
  4. ssh-account 서비스 계정을 삭제합니다.

    gcloud iam service-accounts delete ssh-account --project $PROJECT_ID
    
  5. 이름이 ssh-example인 네트워크를 삭제합니다.

    gcloud compute networks delete ssh-example --project $PROJECT_ID
    

다음 단계

  • 전체 코드 샘플을 다운로드하고 확인합니다. 전체 샘플에는 이러한 모든 메소드를 함께 사용하는 방법에 대한 작은 예가 포함되어 있습니다. 다운로드한 후 필요에 맞게 변경하여 실행하세요.
  • Compute Engine API 참조OS 로그인 API 참조에서 이 API를 사용하여 다른 작업을 수행하는 방법을 검토합니다.
  • 앱 만들기를 시작합니다.
이 페이지가 도움이 되었나요? 평가를 부탁드립니다.

다음에 대한 의견 보내기...

Compute Engine 문서