如何创建带特定服务账号设置的Google Compute Engine实例?
当你在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 个回答
顺便说一下,在GCE(谷歌云引擎)中,你通常会得到两个默认的服务账号:
- -compute@developer.gserviceaccount.com
- @cloudservices.gserviceaccount.com
注意这两个邮箱后缀的不同(developer.gserviceaccount.com
和 cloudservices.gserviceaccount.com
)。看起来,即使你使用自己的服务账号,并且它有Owner
(拥有者)角色,也无法访问<number>@cloudservices.gserviceaccount.com
这个账号,只能访问第一个账号(<number>-compute@developer.gserviceaccount.com
)。
在我的情况下,当我尝试用自己的服务账号创建一个实例,并指定这个实例将使用上面提到的第二个服务账号时,出现了错误。一旦我修改请求,让实例使用第一个账号,就成功了。
我觉得你需要创建一个新的服务账号,才能从非GCE实例使用这个API。你提到的那个服务账号只能在GCE实例里用。
- 首先,去云控制台 > 项目 > API 和身份验证 > 凭据。
- 创建新的客户端ID。
- 选择服务账号。
- 下载.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()