首页 > 解决方案 > 如何使用 node.js 更改带有 GCP Cloud Function 的 VM/实例标签?

问题描述

我正在 GCP 中测试一个云函数,我想用云函数修改我的计算实例的标签,即将标签“status=active”更改为“status=tobedeleted”。

有没有办法用 Cloud Function 和 node.js 做到这一点?

看起来方法 compute.instances.setLabels 需要额外的库?

我已经创建了 Cloud Function 来停止/启动实例。

这是错误:

资源:{...}
严重性:“错误”
textPayload:“{ 错误:在 Gaxios.request (/srv/node_modules/googleapis-common/node_modules/gaxios/build/src/gaxios.js:70:23) 处需要登录process._tickDomainCallback (internal/process/next_tick.js:229:7) 响应:{ config: { url: ' https://www.googleapis.com/compute/v1/projects/wpress-v1/zones/us-central1 -a/instances/instance-1/setLabels?labels%5Bis-scheduled%5D=manual ', 方法: 'POST', paramsSerializer: [Function], headers: [Object], params: [Object], validateStatus: [Function ],重试:true,responseType:'json',retryConfig:[Object]},数据:{错误:[Object]},

然后这是我的代码:

const Compute = require('@google-cloud/compute');
/*const compute = new Compute();*/
const {google} = require('googleapis');
/*const google = require('@google-cloud/googleapis');*/
var compute = google.compute('v1');

exports.setInstanceScheduleMode = (event, context, callback) => {
  try {

    const payload = _validatePayload(
      JSON.parse(Buffer.from(event.data, 'base64').toString())
    );

    var request = {
    project: 'wpress-v1',  
    zone: 'us-central1-a', 
    instance: 'instance-1', 
    labels: {
    "is-scheduled": "manual"
  },
    auth: google.authClient,
    };
    compute.instances.setLabels(request, function(err, response) {
    if (err) {
      console.error(err);
      return;
    }


    console.log(JSON.stringify(response, null, 2));
  });
  } catch (err) {
    console.log(err);
    callback(err);
  }
};
// [END functions_start_instance_pubsub]
function _validatePayload(payload) {
  if (!payload.zone) {
    throw new Error(`Attribute 'zone' missing from payload`);
  } else if (!payload.label) {
    throw new Error(`Attribute 'label' missing from payload`);
  }
  else if (!payload.instance) {
    throw new Error(`Attribute 'instance' missing from payload`);
  }
  return payload;
}
function authorize(callback) {
  google.auth.getClient({
    scopes: ['https://www.googleapis.com/auth/cloud-platform']
  }).then(client => {
    callback(client);
  }).catch(err => {
    console.error('authentication failed: ', err);
  });
}

标签: node.jsgoogle-cloud-platformgoogle-cloud-functionsgoogle-compute-engine

解决方案


这段代码中发生了很多事情。这不是一个简单的操作,我希望文档中有更多示例说明如何执行此操作。

首先,似乎@google-cloud/compute惯用库不支持setLabelsVMs 对象上的函数,因此我们被迫使用节点 REST 库,它不太容易使用。您编写的代码似乎以某种令人困惑的方式混合了两者,但大部分已经在使用 REST API,因此我们可以从那里开始。作为参考,setLabels REST API 文档

其次,您遇到的身份验证错误是因为您没有为 REST API 正确集成 authClient,特别是通过授予它正确的范围。(值得注意的authorize()是,与示例代码不同,该方法永远不会被调用)。至少需要调用它来请求https://www.googleapis.com/auth/compute范围,尽管cloud-platform范围也可以工作,因为它具有更多特权。这就是导致您立即发生身份验证错误的原因。

您也可能在没有必要角色的情况下将云功能作为 IAM 帐户运行,但默认计算引擎和默认应用程序引擎帐户都应该能够执行此操作,因此似乎没有请求范围.

最后,即使这有效,您会发现 setLabels 方法需要当前标签值的指纹,或者它会返回一个CONDITION_FAILURE-- 本质上,当您调用 setLabels 时,您将完全替换实例上的标签,因此 API想要确保两个呼叫者不会同时竞争。

总之,这导致我们这样做(为简单起见,我使用了一个 HTTP 函数,但当然你也可以使用现有的触发器):

const { google } = require('googleapis');
const computeClient = google.compute('v1');

exports.labelInstance = async (req, res) => {
  // First, get the auth scope we need.  Thankfully cloud functions runs with 
  // application default credentials, so we don't need to do anything with keys, etc
  // as long as the service account we are configured to run as has the right permissions.
  //
  // We only need the compute scope, we don't need all of cloud-platform, so limit ourselves to that.

  const auth = new google.auth.GoogleAuth({
    scopes: ['https://www.googleapis.com/auth/compute']
  });
  const authClient = await auth.getClient();

  // Build our request
  var baseRequest = {
    project: 'YOUR-PROJECT-NAME',  
    zone: 'us-central1-a', 
    instance: 'instance-1', 
    auth: authClient
  };

  // We need to get the existing labels and fingerprint first. 
  return computeClient.instances.get(baseRequest).then(result => {
    // We need all the fields from baseRequest again, and we want to keep the old labels.
    // I'm sort of cheating here, since it isn't a deep copy, but it works within the
    // scope of this function.
    setRequest = baseRequest;

    // As setLabels is a POST request, we need to put the parameters in the requestBody.
    setRequest.requestBody = {
      labels: result.data.labels || {},
      labelFingerprint: result.data.labelFingerprint  // Needed to avoid CONDITION_FAILURE
    };

    // And add our new label...
    setRequest.requestBody.labels['my-new-label'] = 'my-new-value';

    return computeClient.instances.setLabels(setRequest);
  }).then(result => {
    console.log('set done');
    console.log(result);
    return res.send('ok');
  }).catch(error => {
    console.error('Error!');
    console.error(error);
    return res.send('error');
  });
};

在您最初的问题中,您想更改标签。显然,您可以调整上面的代码以从使用您喜欢的指纹检索到的集合中删除任何标签,您不必全部复制它们。

另请注意,上述代码实际上并没有等待操作完成(因为操作是异步的——返回的结果可能处于 RUNNING 状态),您需要进一步使用 REST API 来检查操作的状态。我没有这样做,因为它有点超出了这个问题的范围,但你可以在这里阅读


推荐阅读