如何创建带特定服务账号设置的Google Compute Engine实例?

2 投票
2 回答
1775 浏览
提问于 2025-04-18 06:31

当你在Google Compute Engine上创建一个实例A时,它会自动附带一个预定义的“默认”服务账号。这意味着你可以通过这个实例A来访问Google的API,并且是用这个“默认”服务账号进行身份验证。

我想做的是设置一个GCE实例,使用一个不同于默认的服务账号。从理论上讲,这应该是可行的,因为GCE的API支持这个功能,但我遇到了一个异常错误:

{ 
"name": "operation-1400060483459-4f958fbc7d7b9-cd817778-b80d1cad",
"operationType": "insert",
"status": "DONE", 
"user": "some_name@developer.gserviceaccount.com",
"error": { 
   "errors": [ { 
      "code": "SERVICE_ACCOUNT_ACCESS_DENIED", 
      "message": "The user does not have access to service account 'some_name@developer.gserviceaccount.com'"
 } ] } }

这是我用Python写的代码,用来设置这个实例:

discovery_service = discovery.build('compute',
                config['compute_api_version'],
                http=SignedJwtAssertionCredentials(
                  service_account_name="some_name@developer.gserviceaccount.com",
                  private_key=key_data,
                  scope='https://www.googleapis.com/auth/compute')
               .authorize(httplib2.Http()))

instance = {}
# sets instance configuration details here
# ...
# ...
instance['serviceAccounts'] = [{
  'email': "some_name@developer.gserviceaccount.com",
  'scopes': ['https://www.googleapis.com/auth/devstorage.full_control',
              'https://www.googleapis.com/auth/compute',
              'https://www.googleapis.com/auth/userinfo.email', ]
}]
discovery_service.instances().insert(project=project, zone=zone, body=instance)

最奇怪的是,这个异常提示说“用户没有权限访问服务账号'some_name@developer.gserviceaccount.com'”,但这里提到的“用户”其实就是'some_name@developer.gserviceaccount.com'自己!这就意味着'some_name@developer.gserviceaccount.com'没有权限访问'some_name@developer.gserviceaccount.com',这听起来真是说不通。

2 个回答

0

顺便说一下,在GCE(谷歌云引擎)中,你通常会得到两个默认的服务账号:

  • -compute@developer.gserviceaccount.com
  • @cloudservices.gserviceaccount.com

注意这两个邮箱后缀的不同(developer.gserviceaccount.comcloudservices.gserviceaccount.com)。看起来,即使你使用自己的服务账号,并且它有Owner(拥有者)角色,也无法访问<number>@cloudservices.gserviceaccount.com这个账号,只能访问第一个账号(<number>-compute@developer.gserviceaccount.com)。

在我的情况下,当我尝试用自己的服务账号创建一个实例,并指定这个实例将使用上面提到的第二个服务账号时,出现了错误。一旦我修改请求,让实例使用第一个账号,就成功了。

1

我觉得你需要创建一个新的服务账号,才能从非GCE实例使用这个API。你提到的那个服务账号只能在GCE实例里用。

  1. 首先,去云控制台 > 项目 > API 和身份验证 > 凭据。
  2. 创建新的客户端ID。
  3. 选择服务账号。
  4. 下载.p12文件,并把它作为私钥加载。(下面有示例)

另外,你还需要从启动磁盘创建一个实例,这个启动磁盘通常是从一些GCE提供的镜像创建的。

这里有一个使用JSON Web Tokens的示例,对我来说是有效的。这个示例是根据这里的文档改编的:https://cloud.google.com/compute/docs/tutorials/python-guide#addinganinstance

from apiclient import discovery
from oauth2client.file import Storage
from oauth2client.client import SignedJwtAssertionCredentials
import httplib2
import os.path

INSTANCE_NAME = 'my-instance'
API_VERSION = 'v1'
GCE_URL = 'https://www.googleapis.com/compute/%s/projects/' % (API_VERSION)
PROJECT_ID = '***'
SERVICE_ACOUNT_CLIENT_ID = '***.apps.googleusercontent.com'
SERVICE_ACCOUNT_EMAIL_ADDRESS = '***@developer.gserviceaccount.com'
GCE_SCOPE = 'https://www.googleapis.com/auth/compute'
ZONE = 'us-central1-a'
DEFAULT_SERVICE_EMAIL = 'default'
DEFAULT_SCOPES = ['https://www.googleapis.com/auth/devstorage.full_control',
                  'https://www.googleapis.com/auth/compute']

SOURCE_IMAGE_URL = 'projects/ubuntu-os-cloud/global/images/ubuntu-1410-utopic-v20141217'


def main():
    f = file('private-key.p12', 'rb')
    oauth_key_data = f.read()
    f.close()

    http = httplib2.Http()
    oauth_storage = Storage('compute-creds.dat')
    oauth_credentials = oauth_storage.get()

    if oauth_credentials is None or oauth_credentials.invalid:
        oauth_credentials = SignedJwtAssertionCredentials(
                          service_account_name=SERVICE_ACCOUNT_EMAIL_ADDRESS,
                          private_key=oauth_key_data,
                          scope=GCE_SCOPE)
        oauth_storage.put(oauth_credentials)
    else:
        oauth_credentials.refresh(http)

    http = oauth_credentials.authorize(http)

    gce_service = discovery.build('compute', 'v1', http=http)



    project_url = '%s%s' % (GCE_URL, PROJECT_ID)
    image_url = '%s%s/global/images/%s' % (
             GCE_URL, 'ubuntu-os-cloud', 'ubuntu-1410-utopic-v20141217')
    machine_type_url = '%s/zones/%s/machineTypes/%s' % (
        project_url, ZONE, 'n1-standard-1')
    network_url = '%s/global/networks/%s' % (project_url, 'default')

    instance = {
        'name': INSTANCE_NAME,
        'machineType': machine_type_url,
        'disks': [{
            'autoDelete': 'true',
            'boot': 'true',
            'type': 'PERSISTENT',
            'initializeParams' : {
              'diskName': INSTANCE_NAME,
              'sourceImage': SOURCE_IMAGE_URL
            }
          }],
        'networkInterfaces': [{
          'accessConfigs': [{
            'type': 'ONE_TO_ONE_NAT',
            'name': 'External NAT'
           }],
          'network': network_url
        }],
        'serviceAccounts': [{
             'email': DEFAULT_SERVICE_EMAIL,
             'scopes': DEFAULT_SCOPES
        }]
      }
    # Create the instance
    request = gce_service.instances().insert(
       project=PROJECT_ID, body=instance, zone=ZONE)
    response = request.execute(http=http)
    response = _blocking_call(gce_service, http, response)

    print response

def _blocking_call(gce_service, auth_http, response):
    """Blocks until the operation status is done for the given operation."""

    status = response['status']
    while status != 'DONE' and response:
        operation_id = response['name']

        # Identify if this is a per-zone resource
        if 'zone' in response:
            zone_name = response['zone'].split('/')[-1]
            request = gce_service.zoneOperations().get(
                project=PROJECT_ID,
                operation=operation_id,
                zone=zone_name)
        else:
            request = gce_service.globalOperations().get(
                project=PROJECT_ID, operation=operation_id)

        response = request.execute(http=auth_http)
        if response:
            status = response['status']
    return response

main()

撰写回答