Node.js 런타임

개요

Node.js 런타임은 애플리케이션의 코드 및 종속 항목을 설치하고 애플리케이션을 실행하는 소프트웨어 스택입니다. 이 런타임은 app.yaml에서 runtime: nodejs로 선언됩니다.

runtime: nodejs
env: flex

가변형 환경의 런타임은 Docker를 사용해 구축합니다. Node.js 런타임은 Ubuntu 16.04를 기반으로 하며 Node.js 런타임의 소스 코드는 GitHub에서 공개적으로 사용할 수 있습니다.

패키지 관리자

배포하는 동안 런타임은 npm 또는 yarn 패키지 관리자를 사용하여 종속 항목을 설치하고 애플리케이션을 시작합니다. 패키지 관리자는 다음 로직으로 설정됩니다.

  • 기본 패키지 관리자는 npm입니다.
  • 애플리케이션의 루트 디렉터리에 yarn.lock 파일이 있는 경우 런타임은 대신 yarn 패키지 관리자를 사용합니다.
  • package-lock.jsonyarn.lock이 모두 있는 경우 배포가 실패하고 오류가 발생합니다. 두 파일이 모두 필요하다면 app.yaml 파일의 skip_files 섹션에 파일 중 하나를 추가하여 사용할 패키지 관리자를 지정할 수 있습니다.

엔진

Node.js 버전

기본 Node.js 엔진은 최신 LTS 출시 버전인 것이 좋습니다. engines 필드를 사용하여 애플리케이션의 package.json 파일에서 다른 Node.js 버전을 지정할 수 있습니다. 예기치 않은 중단을 방지하기 위해서는 Node.js 버전을 지정해야 합니다.

다음 예에서는 런타임에서 최신 Node 9 출시 버전을 사용하도록 구성합니다.

{
  "engines": {
    "node": "9.x"
  }
}

engines.node 속성은 semver 범위일 수 있습니다. 이 속성을 지정하면 런타임은 semver 범위와 일치하는 Node.js의 최신 버전을 다운로드하고 설치합니다. 일치하는 버전이 없으면 애플리케이션은 배포되지 않으며 런타임이 오류 메시지를 반환합니다.

패키지 관리자 버전

런타임 이미지는 최신 yarn 출시 버전 및 최신 Node.js LTS 출시 버전에서 사용할 수 있는 npm 출시 버전을 사용하는 것이 좋습니다.

engines 필드를 사용하여 애플리케이션의 package.json 파일에서 다른 패키지 관리자 버전을 지정할 수 있습니다. 이 경우 런타임은 배포에 사용된 패키지 관리자가 engines 필드에 나열된 사양과 일치하는 버전인지 확인합니다.

yarnnpm 버전 사양이 모두 제공되면 필요한 경우 배포에 사용되는 패키지 관리자만이 업데이트됩니다. 이렇게 하면 실제로 애플리케이션을 배포하는 데 사용되지 않는 패키지 관리자의 커스텀 버전을 설치하지 않음으로써 배포 시간을 절약할 수 있습니다.

다음 예에서는 런타임에서 npm의 커스텀 버전을 사용하도록 구성합니다.

{
  "engines": {
    "npm": "5.x"
  }
}

다음 예에서는 런타임에서 yarn의 커스텀 버전을 사용하도록 구성합니다.

{
  "engines": {
    "yarn": ">=1.0.0 <2.0.0"
  }
}

engines.npmengines.yarn 속성 모두 semver 범위일 수 있습니다.

종속 항목

배포하는 동안 런타임은 npm 또는 yarn 패키지 관리자를 사용하여 npm install 또는 yarn install을 실행해 종속 항목을 설치합니다. 런타임에서 사용할 패키지 관리자를 선택하는 방법에 대한 자세한 내용은 패키지 관리자 섹션을 참조하세요.

Google App Engine에서 Node.js 패키지를 관리하는 방법에 대한 자세한 내용은 Node.js 라이브러리 사용을 참조하세요.

네이티브 확장 프로그램이 필요한 Node.js 패키지를 사용 설정하려면 다음 Ubuntu 패키지가 Docker 이미지에 사전 설치되어 있어야 합니다.

추가로 운영체제 수준의 종속 항목이 필요한 애플리케이션인 경우 이 런타임을 기반으로 커스텀 런타임을 사용해 적절한 패키지를 설치해야 합니다.

애플리케이션 시작

런타임은 package.json에 지정된 명령어를 사용하는 npm start를 사용해 애플리케이션을 시작합니다. 예를 들면 다음과 같습니다.

"scripts": {
  "start": "node app.js"
}

시작 스크립트는 PORT 환경 변수가 지정한 포트(일반적으로 8080)에서 HTTP 요청에 응답하는 웹 서버를 시작해야 합니다.

런타임 확장

표준 Node.js 런타임을 사용하여 커스텀 런타임을 만들 수 있습니다. 이렇게 하려면 gcloud beta app gen-config --custom을 사용해 기본 Dockerfile.dockerignore 파일을 만듭니다. 그런 다음 필요에 따라 파일을 맞춤설정할 수 있습니다. Dockerfile을 만드는 경우 app.yaml에서 runtime: nodejs 대신 runtime: custom을 지정해야 합니다.

HTTPS 및 전달 프록시

App Engine은 부하 분산기에서 HTTPS 연결을 종료하고 애플리케이션에 요청을 전달합니다. 일부 애플리케이션에서는 원본 요청 IP 및 프로토콜을 결정해야 합니다. 사용자의 IP 주소는 표준 X-Forwarded-For 헤더에서 사용할 수 있습니다. 이 정보가 필요한 애플리케이션은 웹 프레임워크가 프록시를 신뢰하도록 구성해야 합니다.

Express.js의 경우 다음과 같은 trust proxy 설정을 사용합니다.

app.set('trust proxy', true);

HTTPS 연결 강제에 대한 자세한 내용은 요청 처리 방법을 참조하세요.

환경 변수

런타임 환경에서 설정되는 환경 변수는 다음과 같습니다.

환경 변수 설명
GAE_INSTANCE 현재 인스턴스의 이름입니다.
GAE_MEMORY_MB 애플리케이션 프로세스에서 사용할 수 있는 메모리 양입니다.
GAE_SERVICE 애플리케이션의 app.yaml 파일에 지정된 서비스 이름입니다. 서비스 이름이 지정되지 않은 경우에는 default로 설정됩니다.
GAE_VERSION 현재 애플리케이션의 버전 라벨입니다.
GOOGLE_CLOUD_PROJECT 애플리케이션과 연결된 프로젝트 ID로, Google Cloud Platform 콘솔에서 확인할 수 있습니다.
NODE_ENV 앱이 배포된 경우 값은 production입니다.
PORT HTTP 요청을 수신할 포트이며 8080으로 설정됩니다.

app.yaml을 사용하여 추가 환경 변수를 설정할 수 있습니다.

메타데이터 서버

애플리케이션의 각 인스턴스에서 Compute Engine 메타데이터 서버를 사용해 호스트 이름, 외부 IP 주소, 인스턴스 ID, 커스텀 메타데이터, 서비스 계정 정보 등의 인스턴스 관련 정보를 쿼리할 수 있습니다. App Engine에서는 인스턴스 별로 커스텀 메타데이터를 설정할 수 없지만 프로젝트 차원의 커스텀 메타데이터를 설정해 App Engine 및 Compute Engine 인스턴스에서 읽는 것은 가능합니다.

이 예의 함수는 메타데이터 서버를 사용해 인스턴스의 외부 IP 주소를 가져옵니다.

const express = require('express');
const request = require('got');

const app = express();
app.enable('trust proxy');

const METADATA_NETWORK_INTERFACE_URL =
  'http://metadata/computeMetadata/v1/' +
  '/instance/network-interfaces/0/access-configs/0/external-ip';

const getExternalIp = async () => {
  const options = {
    headers: {
      'Metadata-Flavor': 'Google',
    },
    json: true,
  };

  try {
    const {body} = await request(METADATA_NETWORK_INTERFACE_URL, options);
    return body;
  } catch (err) {
    console.log('Error while talking to metadata server, assuming localhost');
    return 'localhost';
  }
};

app.get('/', async (req, res, next) => {
  try {
    const externalIp = await getExternalIp();
    res
      .status(200)
      .send(`External IP: ${externalIp}`)
      .end();
  } catch (err) {
    next(err);
  }
});

const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
  console.log(`App listening on port ${PORT}`);
  console.log('Press Ctrl+C to quit.');
});