2017-07-12 22 views
1

我試圖複製EC2圖像以進行加密。由於關於EC2帳單的神祕錯誤,無法複製圖像帳單產品代碼

命令行我的問題是:

$ aws ec2 copy-image --name encrypted-kafka-201707121432 --source-region ap-southeast-2 --encrypted --source-image-id ami-2a617249 --region ap-southeast-2 

這導致下面的錯誤正在發出:

An error occurred (InvalidRequest) when calling the CopyImage operation: Images with EC2 BillingProduct codes cannot be copied to another AWS account. 

我已經環顧四周,我明白,這個錯誤通常看到複製的Windows時AMI(例如here),它與SO(here)上的此問題類似。

然而,這個形象是不是來自市場,而且它不是Windows AMI,和它沒有任何ProductCodes它:

$ aws ec2 describe-images --image-ids ami-2a617249 --region ap-southeast-2 | \ 
> jq '.Images | .[] | .ProductCodes' 
null 

與此相比,另一個是做:

$ aws ec2 describe-images --image-ids ami-00280263 --region ap-southeast-2 | \ 
> jq '.Images | .[] | .ProductCodes' 
[ 
    { 
    "ProductCodeType": "marketplace", 
    "ProductCodeId": "dsli9z1o9amfv5g2hsmdj1pph" 
    } 
] 

使用Packer烘焙圖像。

我沒有想法。爲什麼會發生這種情況 - 我做錯了什麼,或者這是無證的行爲?

+0

從市場實例的AMI產生的情況下,或者是在任何與Marketplace實例關聯的方式?另請參閱https://serverfault.com/questions/775946/aws-encrypted-ebs-boot-volumes-for-windows-instances和http://blog.open-tribute。org/2017/04/18/ami-copy-failed-billingproduct/ –

+0

據我所知,除非我被誤導,否則不會。我想排除這一點,我需要自己完成所有上游的AMI烘焙,或者有沒有一種測試方法? –

+0

畢竟答案是「是」。下面的完整解釋。 –

回答

0

事實證明,這個AMI的後代確實來自亞馬遜市場。

烘烤管道的簡化版本是:

帳戶A:

市場AMI(AMI-XXXXXXXX) - >封隔器的構建(AMI-YYYYYYYY) - >共享B帳戶

B帳戶:

然後我發出:

aws ec2 copy-image --encrypted --source-image ami-yyyyyyyy 

,並重新可察覺:

An error occurred (InvalidRequest) when calling the CopyImage operation: Images with EC2 BillingProduct codes cannot be copied to another AWS account. 

從帳戶BI可以檢查所有者,即與我分享它的帳戶:

$ aws ec2 describe-images --image-id ami-yyyyyyyy --region ap-southeast-2 \ 
> --query 'Images[0].OwnerId' 

這回戶口A

的16位數的帳戶ID在這一點有助於理解,使用Packer烘烤AMI會導致產品代碼顯示爲顯然丟失。但是,這些產品代碼仍然存在,亞馬遜支持可以看到它們。它需要致電亞馬遜支持部門確認此部分。

要解決這一點,我們正在使用這個Python腳本,這似乎令人難以置信的複雜,但至少它的工作:

#!/usr/bin/env python 
import argparse 
import boto3 
import os 
import random 
import sys 
import time 

def copy_ec2_image(**kwargs): 
    client = get_ec2_client() 
    if os.environ.get('DATE_TIME'): 
    kwargs['name'] += "-" + os.environ['DATE_TIME'] 
    if kwargs['encrypted']: 
    kwargs['name'] = "encrypted-" + kwargs['name'] 
    sys.stdout.write("Creating the AMI: %s\n" %kwargs['name']) 
    sys.stdout.flush() 
    response = client.copy_image(DryRun=False, SourceRegion=kwargs['source_region'], SourceImageId=kwargs['source_ami_id'], Name=kwargs['name'], Description=kwargs['description'], Encrypted=kwargs['encrypted'], KmsKeyId=kwargs['kms_key_id']) 
    sys.stdout.write("AMI: %s\n" %response['ImageId']) 
    sys.stdout.flush() 
    return response['ImageId'], kwargs 

def create_ec2_image(instance_id, **kwargs): 
    client = get_ec2_client() 
    if os.environ.get('DATE_TIME'): 
    kwargs['name'] += "-" + os.environ['DATE_TIME'] 
    kwargs['name'] = "unencrypted-" + kwargs['name'] 
    sys.stdout.write("Creating the AMI: %s\n" %kwargs['name']) 
    sys.stdout.flush() 
    response = client.create_image(DryRun=False, InstanceId = instance_id, Name=kwargs['name']) 
    return response['ImageId'], kwargs 

def deregister_ec2_image(ami_id): 
    client = get_ec2_client() 
    sys.stdout.write("Deregistering the AMI: %s\n" %ami_id) 
    sys.stdout.flush() 
    return client.deregister_image(DryRun=False, ImageId=ami_id) 

def get_args(): 
    parser = argparse.ArgumentParser() 
    parser.add_argument('--source-region', action='store', required=False, metavar='SOURCE_AMI_REGION', dest='source_region', default='ap-southeast-2', help='The name of the region that contains the AMI to copy.') 
    parser.add_argument('--description', action='store', required=False, metavar='DESCRIPTION', dest='description', default='', help=' A description for the new AMI in the destination region.') 
    parser.add_argument('--encrypted', required=False, action='store_true', help='Specifies whether the destination snapshots of the copied image should be encrypted.') 
    parser.add_argument('--kms-key-id', action='store', required=False, metavar='KMS_KEY_ID', dest='kms_key_id', default='', help='The full ARN of the AWS Key Management Service (AWS KMS) CMK to use when encrypting the snapshots of an image during a copy operation. This parameter is only required if you want to use a non-default CMK; if this parameter is not specified, the default CMK for EBS is used.') 
    parser.add_argument('--source-image-id', action='store', required=True, metavar='SOURCE_AMI_ID', dest='source_ami_id', help='The ID of the AMI to copy.') 
    parser.add_argument('name', action='store', help='The name of the new AMI in the destination region.') 
    return parser.parse_args() 

def get_account_id(): 
    try: 
    # We're running in an ec2 instance, get the account id from the 
    # instance profile ARN 
    return json.loads(urllib2.urlopen('http://169.254.169.254/latest/meta-data/iam/info/', None, 1).read())['InstanceProfileArn'].split(':')[4] 
    except: 
    pass 
    try: 
    # We're not on an ec2 instance but have api keys, get the account 
    # id from the user ARN 
    return boto3.client('iam').get_user()['User']['Arn'].split(':')[4] 
    except: 
    pass 
    return False 

def get_ec2_client(): 
    return boto3.client('ec2') 

def get_ec2_image_account_id(ami_id): 
    client = get_ec2_client() 
    response = client.describe_images(DryRun=False, ImageIds=[ami_id]) 
    return response['Images'][0]['ImageLocation'].split('/')[0] 

def get_ec2_image_status(ami_id, **kwargs): 
    client = get_ec2_client() 
    sys.stdout.write("Waiting for AMI to become ready...\n") 
    sys.stdout.flush() 
    while True: 
    response = client.describe_images(DryRun=False, ImageIds=[ami_id]) 
    if response['Images'][0]['State'] == 'available': 
     sys.stdout.write("AMI successfully created: %s\n" %ami_id) 
     sys.stdout.flush() 
     if os.environ.get('JOB_NAME'): 
     filename = os.environ['JOB_NAME'] + "_ID.txt" 
     else: 
     filename = kwargs['name'].title() + "_AMI_ID.txt" 
     fd = open(filename, 'w') 
     fd.write(ami_id + "\n") 
     fd.close() 
     return 0 
    sys.stdout.write("state: %s\n" %response['Images'][0]['State']) 
    sys.stdout.flush() 
    time.sleep(10) 

def get_ec2_instance_status(instance_id, status): 
    client = get_ec2_client() 
    if status == 'running': 
    sys.stdout.write("Waiting for instance (%s) to become ready...\n" %instance_id) 
    sys.stdout.flush() 
    while True: 
     response = client.describe_instances(DryRun=False, InstanceIds=[ instance_id ]) 
     try: 
     if response['Reservations'][0]['Instances'][0]['State']['Name'] == status: 
      break 
     time.sleep(1) 
     except: 
     pass 
    sys.stdout.write("Waiting for instance (%s) to become healthy...\n" %instance_id) 
    sys.stdout.flush() 
    while True: 
     response = client.describe_instance_status(DryRun=False, InstanceIds=[ instance_id ]) 
     try: 
     if response['InstanceStatuses'][0]['SystemStatus']['Status'] == response['InstanceStatuses'][0]['InstanceStatus']['Status'] == 'ok': 
      break 
     time.sleep(1) 
     except: 
     pass 
    sys.stdout.write("Instance up and running!!\n") 
    sys.stdout.flush() 
    return 
    elif status == 'stopped': 
    sys.stdout.write("Waiting for instance (%s) to stop...\n" %instance_id) 
    sys.stdout.flush() 
    while True: 
     response = client.describe_instances(DryRun=False, InstanceIds=[ instance_id ]) 
     try: 
     if response['Reservations'][0]['Instances'][0]['State']['Name'] == status: 
      break 
     time.sleep(1) 
     except: 
     pass 
    return 
    elif status == 'terminated': 
    sys.stdout.write("Waiting for instance (%s) to terminate...\n" %instance_id) 
    sys.stdout.flush() 
    while True: 
     response = client.describe_instances(DryRun=False, InstanceIds=[ instance_id ]) 
     try: 
     if response['Reservations'][0]['Instances'][0]['State']['Name'] == status: 
      break 
     time.sleep(1) 
     except: 
     pass 
    return 

def get_ec2_subnet_ids(tag_name): 
    client = get_ec2_client() 
    response = client.describe_subnets(DryRun=False, Filters=[ { 'Name': 'tag:Name', 'Values': [ tag_name ] } ]) 
    return [subnet['SubnetId'] for subnet in response['Subnets']] 

def launch_ec2_instance(**kwargs): 
    client = get_ec2_client() 
    sys.stdout.write("Launching a source AWS instance...\n") 
    sys.stdout.flush() 
    if os.environ.get('AWS_BACKEND_SUBNET_IDS'): 
    subnet_id = random.choice(os.environ.get('AWS_BACKEND_SUBNET_IDS').split(',')) 
    else: 
    subnet_id = random.choice(get_ec2_subnet_ids('*-BackEnd-*')) 
    response = client.run_instances(DryRun=False, ImageId=kwargs['source_ami_id'], InstanceType='c4.2xlarge', MinCount=1, MaxCount=1, SubnetId=subnet_id) 
    instance_id = response['Instances'][0]['InstanceId'] 
    sys.stdout.write("Instance ID: %s\n" %instance_id) 
    get_ec2_instance_status(instance_id, 'running') 
    return instance_id 

def stop_ec2_instance(instance_id): 
    client = get_ec2_client() 
    sys.stdout.write("Stopping the source AWS instance...\n") 
    sys.stdout.flush() 
    response = client.stop_instances(DryRun=False, InstanceIds=[ instance_id ]) 
    get_ec2_instance_status(instance_id, 'stopped') 

def terminate_ec2_instance(instance_id): 
    client = get_ec2_client() 
    sys.stdout.write("Terminating the source AWS instance...\n") 
    sys.stdout.flush() 
    response = client.terminate_instances(DryRun=False, InstanceIds=[ instance_id ]) 
    get_ec2_instance_status(instance_id, 'terminated') 

def main(): 
    args = get_args() 
    try: 
    if get_account_id() == get_ec2_image_account_id(vars(args)['source_ami_id']): 
     ami_id, kwargs = copy_ec2_image(**vars(args)) 
     get_ec2_image_status(ami_id, **kwargs) 
    else: 
     instance_id = launch_ec2_instance(**vars(args)) 
     stop_ec2_instance(instance_id) 
     unencrypted_ami_id, kwargs = create_ec2_image(instance_id, **vars(args)) 
     get_ec2_image_status(unencrypted_ami_id, **kwargs) 
     terminate_ec2_instance(instance_id) 
     vars(args)['source_ami_id'] = unencrypted_ami_id 
     encrypted_ami_id, kwargs = copy_ec2_image(**vars(args)) 
     get_ec2_image_status(encrypted_ami_id, **kwargs) 
     deregister_ec2_image(unencrypted_ami_id) 
    except KeyboardInterrupt: 
    sys.exit("User aborted script!") 

if __name__ == '__main__': 
    main()