Compute Module 컨테이너와 환경 변수
이미지는 코드와 의존성 및 시작 설정을 담은 실행 템플릿이며, 컨테이너는 그 이미지로 실행한 인스턴스입니다. Compute Module은 호환되는 이미지를 Foundry Artifacts에 게시한 뒤 실행합니다.
함수 구현은 Python, Java, TypeScript SDK를 사용합니다. 외부 연결은 소스, 같은 복제본 안의 파일 공유는 볼륨으로 구성합니다.
이미지 조건
- linux/amd64 대상으로 빌드합니다.
- root가 아닌 숫자 UID로 실행합니다.
- digest 또는 latest 이외의 태그를 지정합니다.
- 노출하는 포트는 1024~65535 범위이며 8945와 8946은 제외합니다.
Docker를 설치하고 docker info로 daemon 상태를 확인할 수 있습니다. Cannot connect to the Docker daemon이면 Docker daemon과 Desktop 실행 상태를 해결한 뒤 빌드합니다.
| Dockerfile 명령 | 역할 |
|---|---|
| FROM | 빌드 단계의 기반 이미지 |
| WORKDIR | 뒤따르는 작업의 기본 디렉터리 |
| RUN | 이미지 빌드 중 설치, 컴파일 등 실행 |
| COPY | 빌드 컨텍스트의 파일을 이미지로 복사 |
| USER | 실행 UID 설정 |
| ENTRYPOINT | 컨테이너 시작 명령 |
| EXPOSE | 앱이 수신하는 포트 명시 |
| ENV | 런타임 환경 변수의 기본값 설정 |
Python 이미지 예제
프로젝트에 Dockerfile, requirements.txt와 src/application.py를 둡니다. requirements.txt에는 실제 앱 의존성을, application.py에는 모듈 구현을 넣습니다. Python SDK의 add 함수를 사용한다면 requirements.txt에 foundry-compute-modules를 넣습니다.
FROM python:3.12
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY src/application.py ./
USER 5001
ENTRYPOINT ["python", "application.py"]
숫자 UID로 실행할 수 있으므로 이름 있는 OS 사용자를 만들 필요는 없습니다. 의존성과 앱 코드를 별도 COPY 단계로 나누면 코드만 바꿀 때 의존성 설치 레이어를 재사용할 수 있습니다.
docker build --platform linux/amd64 -t myimage:0.0.0 .
Foundry에서 Artifacts 저장소를 만들고 Publish > Docker를 선택합니다. 해당 저장소가 표시하는 로그인과 push 명령으로 이미지를 게시한 뒤 Compute Module의 컨테이너로 선택합니다.
로그 수집
Configure의 Containers에서 컨테이너 행을 선택하면 로그 수집을 켜거나 끄고 형식과 소스를 정할 수 있습니다.
SLS
SLS는 메시지와 메타데이터를 구조화한 JSON 로그 형식입니다. service.1 로그에는 level, time, origin, thread, message 및 params 같은 필드가 들어갑니다. 아래 값은 형식을 보여 주는 가상 예입니다.
{
"type": "service.1",
"level": "INFO",
"time": "2024-01-15T12:00:00.000Z",
"origin": "com.example.MyClass",
"thread": "main",
"message": "Processing request",
"params": {"requestId": "example-request"},
"uid": "example-log-id",
"sid": "example-session-id"
}
SLS를 선택하면 일반 System.out.println 출력은 SLS 로그로 수집하지 않습니다. Java SDK의 SafeLogger 또는 Python SDK의 get_logger처럼 형식에 맞는 로거를 사용합니다.
이전 DeployedAppRuntime을 사용하는 앱에서는 다음과 같이 기록할 수 있습니다. 해당 SDK는 Include runtime을 OFF로 설정해야 합니다.
package myproject;
import com.palantir.interactive.module.api.SafeArg;
import com.palantir.interactive.module.api.UnsafeArg;
import com.palantir.interactive.module.tasks.deployedapps.DeployedAppRuntime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class DeployedApp {
private static final Logger LOG = LoggerFactory.getLogger(DeployedApp.class);
public static void main(String[] args) {
DeployedAppRuntime.builder()
.addQueryRunner(DeployedApp::hello, String.class, String.class, "hello")
.buildAndStart();
}
static String hello(String name) {
LOG.info("Greeting requested", SafeArg.of("operation", "hello"));
LOG.info("Greeting recipient", UnsafeArg.of("name", name));
return "Hello, " + name + "!";
}
private DeployedApp() {}
}
SafeArg는 비민감 값, UnsafeArg는 민감할 수 있는 값에 사용합니다. 비밀 자체를 로그에 기록하는 방식으로 사용하지 않습니다. Python은 get_logger(__name__)와 logger.setLevel(logging.INFO)로 로거를 만들고 set_internal_log_level()로 SDK 내부의 상세 정도를 조절합니다. 수준은 DEBUG, INFO, WARNING, ERROR, CRITICAL 순서입니다.
Plaintext
Plaintext 형식은 출력을 SLS 로그의 message 필드에 넣습니다. 일반 텍스트를 그대로 읽을 수 있으며, 입력에 SLS JSON이 있어도 JSON 문자열이 message에 나타납니다. 일반 출력과 구조화 출력을 함께 수집해야 할 때 사용할 수 있습니다.
STDOUT 또는 파일
| 소스 | 조건 |
|---|---|
| Standard output | /bin/sh 실행 파일과 set, tee 명령을 지원해야 함 |
| stderr 포함 | true이면 stdout과 stderr를 동기화하여 한 스트림으로 합침 |
| Log files | Directory path와 File path patterns 지정. 각 패턴에 디렉터리 경로 포함 |
예를 들어 Directory path가 /var/log이면 패턴으로 /var/log/foo/*.log 및 /var/log/bar.txt를 지정할 수 있습니다. 로그 파일 방식을 쓸 때 지정한 기본 디렉터리는 모듈 시작 시 비어 있어야 합니다.
사용자 환경 변수
환경 변수는 이미지를 다시 빌드하지 않고 앱 설정을 바꿀 때 사용합니다. 예를 들어 production 변수를 true로 지정하면 INFO, 그 밖의 경우에는 DEBUG 로그를 사용하는 코드를 작성할 수 있습니다.
import logging
import os
logging.basicConfig(
level=logging.INFO if os.environ.get("production") == "true" else logging.DEBUG
)
비밀은 이미지의 ENV나 코드에 넣지 않고 소스 자격 증명 또는 애플리케이션 권한의 제공 경로를 사용합니다.
예약 환경 변수
다음 변수는 플랫폼이 제공하므로 덮어쓸 수 없습니다. 파일 경로인 변수를 토큰 값 자체로 전송하지 않도록 구분합니다.
| 변수 | 값과 용도 |
|---|---|
| CLIENT_ID | Functions 모드의 Application permissions에 연결한 앱의 client ID |
| CLIENT_SECRET | 위 앱의 client secret. client credentials 토큰 교환에 사용 |
| RUNTIME_HOST | 내부 Compute Module 서비스의 호스트 |
| RUNTIME_PORT | 내부 서비스 포트. 예: 8945 |
| RUNTIME_API | 호스트와 포트를 포함한 내부 서비스 주소 |
| GET_JOB_PATH | 작업 조회 URI 경로 |
| GET_JOB_URI | 작업 조회 전체 URI |
| POST_RESULT_PATH | 결과 전송 URI 경로 |
| POST_RESULT_URI | 결과 전송 기본 URI. 뒤에 jobId를 붙여 사용 |
| POST_SCHEMA_URI | 함수 명세 게시 전체 URI |
| MAX_CONCURRENT_TASKS | 복제본당 최대 작업 수. 시작 시 읽으며 실행 중 값 변경을 반영하지 않음 |
| SOURCE_CREDENTIALS | 소스 API 이름을 최상위 키로 갖는 비밀 JSON 파일 경로. 소스를 연결했을 때 제공 |
| SOURCE_CONFIGURATIONS_PATH | 소스별 비밀 및 연결 메타데이터를 담은 JSON 파일 경로 |
| DEFAULT_CA_PATH | 내부 서비스 연결의 CA PEM 파일 경로. 사용자 클라이언트의 TLS 검증에 사용 |
| BUILD2_TOKEN | Pipeline 입력/출력으로 범위가 제한된 토큰 파일 경로 |
| COMPUTE_SESSION_ID | 컨테이너가 실행되는 복제본의 세션 식별자. 복제본마다 다름 |
| FOUNDRY_COMPUTE_MODULE_RID | Compute Module의 RID |
| MODULE_AUTH_TOKEN | 내부 서비스 요청의 Module-Auth-Token 헤더에 넣을 토큰 파일 경로 |
| FOUNDRY_ONTOLOGY_RID | Marketplace 설치 프로젝트의 대상 온톨로지 RID |
내부 경로의 예는 /interactive-module/api/internal-query/job, /interactive-module/api/internal-query/results, /interactive-module/api/internal-query/schemas입니다. 사용자 클라이언트는 실제 제공된 URI를 사용합니다.
import os
from pathlib import Path
module_auth_token = Path(os.environ["MODULE_AUTH_TOKEN"]).read_text().strip()
ca_path = os.environ["DEFAULT_CA_PATH"]
job_uri = os.environ["GET_JOB_URI"]
소스 JSON의 최상위 키는 소스 API 이름입니다. 연결 URL과 secrets도 해당 소스 항목 안에서 읽습니다. 소스별 파일 구조를 따릅니다.
Marketplace에서 FOUNDRY_ONTOLOGY_RID는 원본 프로젝트가 아니라 설치 대상 프로젝트의 온톨로지입니다. 생성 OSDK를 초기화할 때 이 값을 사용해야 하며 온톨로지 엔터티 API 이름은 원본과 대상에서 일치해야 합니다.