본문으로 이동

terraform-aws-mediawiki-chart-renderer를 공개합니다

.
1700년부터 1780년까지 덴마크·노르웨이와의 수출입액을 두 개의 꺾은선으로 그린 손으로 채색한 옛 그래프.
윌리엄 플레이페어, 1786년, 퍼블릭 도메인

안녕하세요, 페미위키 개발팀 낙엽1124입니다. 미디어위키를 운영하고 계시다면 그래프 확장 기능(Extension:Graph)이 보안 문제로 내려간 뒤 차트 확장 기능(Extension:Chart)이 그 자리를 이어받았다는 소식을 들으셨을 것 같습니다.[1] 그런데 차트 확장 기능을 위키미디어 재단 바깥에서 쓰려면 차트를 그려 줄 렌더러를 직접 마련해야 합니다. 페미위키는 이 렌더러를 AWS Lambda에 올려 2026년 10월 28일부터 문서에 차트를 넣을 수 있게 했고, 같은 구성을 다른 위키에서도 쓸 수 있도록 terraform-aws-mediawiki-chart-renderer라는 OpenTofu 모듈로 공개합니다. 이 글에서는 AWS를 쓰는 미디어위키에 이 모듈을 붙이는 방법을 순서대로 안내드리겠습니다.

렌더러가 필요한 이유

차트 확장 기능은 PHP만으로 차트를 그리지 못하고, Node.js로 만든 렌더러가 SVG를 그려 줘야 합니다. 확장 기능은 위키 서버에서 node를 직접 실행하거나, $wgChartServiceUrl에 적은 HTTP 서비스로 차트 정의를 보내 SVG를 돌려받는 두 방법으로 렌더러를 부를 수 있습니다.[2] 위키미디어 재단은 뒤의 방법을 쓰지만 그 서비스는 재단 내부망에서만 닿기 때문에 다른 위키가 빌려 쓸 수는 없습니다.[3]

위키 서버에서 node를 실행하는 쪽은 생각보다 손이 많이 갑니다. 렌더러는 Node.js 20 이상을 요구하니 데비안 bookworm처럼 Node.js 18이 들어 있는 배포판에서는 따로 설치해야 하고, 미디어위키가 셸 명령에 거는 기본 300MB의 가상 메모리 한도($wgMaxShellMemory) 아래에서는 Node.js 22가 아예 시작하지 못하니 한도를 1GB쯤으로 올려야 합니다. 그런데 이 한도는 다른 셸 명령에도 똑같이 적용되고, 차트를 그릴 때마다 위키 서버의 메모리를 그만큼 쓰게 됩니다. EC2 인스턴스 한 대로 위키를 운영하신다면 렌더러를 HTTP 서비스로 떼어 내는 편이 낫고, 차트는 문서를 저장하거나 파싱할 때만 그리면 되니 요청이 올 때만 도는 Lambda가 잘 맞습니다.

구성

모듈이 만드는 것은 렌더러를 담은 Lambda 함수 하나와 그 함수 URL입니다. 함수는 arm64 Node.js 22 런타임에서 메모리 256MB로 돌고, 차트 하나를 그리는 데 11~98ms가 걸립니다.[4]

함수 URL은 인터넷의 누구나 부를 수 있으니 위키가 보낸 요청만 받도록 막아야 하는데, 차트 확장 기능은 렌더러에 요청을 보낼 때 Content-Type 말고는 헤더를 붙이지 못합니다. 그래서 서명 헤더가 필요한 함수 URL의 IAM 인증은 쓸 수 없고, 인증 정보를 $wgChartServiceUrl의 주소 안에 넣어야 합니다. 이때 고정된 비밀값을 넣으면 설정 파일과 로그에 그대로 남고 한번 새면 바꾸기 전까지 계속 쓸 수 있으니, 모듈은 대신 OpenID Connect 발급자가 서명한 JWT를 주소에 붙이게 했습니다. 함수는 발급자의 공개키로 서명을 검증하고 발급자, 대상(aud), 주체(sub), 만료 시각이 맞을 때만 차트를 그립니다.

AWS에서는 STS의 GetWebIdentityToken이 IAM 역할에 대해 이런 토큰을 발급해 줍니다.[5] 위키 서버의 IAM 역할로 토큰을 받아 쓰면 위키 쪽에 저장해 둘 비밀값이 하나도 없고, 토큰은 길어야 한 시간이면 만료됩니다. 아래에서는 이 방법을 기준으로 설명드리겠습니다.

모듈 배포

먼저 계정에서 IAM의 아웃바운드 웹 ID 페더레이션을 켜야 STS가 토큰을 발급해 줍니다. 아래의 aws_iam_outbound_web_identity_federation 리소스가 계정 전체에서 이 기능을 켜고(AWS 프로바이더 6.26 이상), 이 리소스를 지우면 기능도 꺼집니다. 그다음 모듈에 발급자와 위키 서버의 IAM 역할을 알려 줍니다. aws_iam_role.wiki는 위키 서버(EC2 인스턴스)가 쓰는 IAM 역할입니다. 모듈은 OpenTofu와 Terraform 모두에서 같은 주소로 쓸 수 있습니다.[6]

resource "aws_iam_outbound_web_identity_federation" "this" {}

module "chart_renderer" {
  # OpenTofu는 registry.opentofu.org에서, Terraform은 registry.terraform.io에서 받습니다.
  source  = "femiwiki/mediawiki-chart-renderer/aws"
  version = "~> 0.1"

  issuer     = aws_iam_outbound_web_identity_federation.this.issuer_identifier
  subjects   = [aws_iam_role.wiki.arn]
  algorithms = ["ES384"]
}

동시 실행은 기본 2개로 묶여 있어서, 누가 주소로 요청을 쏟아붓더라도 비용이 일정 이상 늘지 않습니다. 다만 요청이 몰리면 위키의 차트 요청도 함께 밀리니, 위키가 크다면 reserved_concurrent_executions로 늘리시면 됩니다. 또 계정의 동시 실행 한도가 낮으면(새 계정은 10인 경우가 있습니다) 예약 동시성을 둘 수 없어 배포가 실패하니, 그럴 때는 서비스 할당량에서 한도를 먼저 늘려 주세요. 배포가 끝나면 url 출력값이 렌더러의 주소입니다.

알립니다 이 모듈은 여러 문서가 한꺼번에 파싱되어 차트 요청이 Lambda의 동시 실행 한도(기본 2개)를 넘으면 일부 차트가 그려지지 않는다는 점이 알려졌습니다. 차트 확장 기능이 거절당한 요청을 다시 보내지 않기 때문에, 그 차트는 문서가 다시 파싱될 때까지 오류로 보입니다. 렌더러 앞에 재시도해 주는 프록시를 두는 방법은 모듈 README에 정리해 두었습니다.

토큰 발급 권한

다음으로 위키 서버의 IAM 역할이 렌더러용 토큰만 받을 수 있도록 정책을 붙입니다. 대상과 서명 알고리즘, 유효 기간을 조건으로 묶어 두면 이 권한으로 다른 서비스용 토큰을 받을 수는 없습니다.

data "aws_iam_policy_document" "chart_token" {
  statement {
    actions   = ["sts:GetWebIdentityToken"]
    resources = ["*"]

    condition {
      test     = "ForAllValues:StringEquals"
      variable = "sts:IdentityTokenAudience"
      values   = ["chart-renderer"]
    }

    condition {
      test     = "StringEquals"
      variable = "sts:SigningAlgorithm"
      values   = ["ES384"]
    }

    condition {
      test     = "NumericLessThanEquals"
      variable = "sts:DurationSeconds"
      values   = ["3600"]
    }
  }
}

resource "aws_iam_role_policy" "chart_token" {
  name   = "ChartRendererToken"
  role   = aws_iam_role.wiki.name
  policy = data.aws_iam_policy_document.chart_token.json
}

토큰 갱신

토큰은 한 시간이면 만료되니 위키 서버가 그보다 자주, 예를 들어 15분마다 cron으로 새 토큰을 받아 PHP만 읽을 수 있는 파일에 써 둡니다. PHP가 반쯤 쓰인 토큰을 읽지 않도록 임시 파일에 쓴 뒤 통째로 교체하고, chown을 위해 root로 실행합니다. AWS CLI v2라면 다음과 같습니다.

umask 077
mkdir -p /run/secrets
aws sts get-web-identity-token --audience chart-renderer \
  --signing-algorithm ES384 --duration-seconds 3600 \
  --query WebIdentityToken --output text > /run/secrets/.chart-renderer-token &&
  chown www-data /run/secrets/.chart-renderer-token &&
  mv /run/secrets/.chart-renderer-token /run/secrets/chart-renderer-token

GetWebIdentityToken은 전역 STS 엔드포인트에서는 동작하지 않으니 리전 엔드포인트를 써야 합니다. AWS CLI v2는 기본값이 리전 엔드포인트라 괜찮지만, AWS CLI v1이나 AWS SDK for PHP는 버전에 따라 전역 엔드포인트를 쓸 수 있으니 sts_regional_endpoints를 regional로 지정해 두시는 편이 안전합니다.[6] 또 이 명령은 2025년 말 이후의 AWS CLI v2에만 있고, PHP로 받으신다면 aws/aws-sdk-php 3.361.0 이상이 필요합니다. /run은 재부팅하면 비워지니 cron과 별도로 부팅할 때도 한 번 실행해 주세요. 페미위키는 PHP로 토큰을 받고 있는데, 그 코드는 docker-mediawiki 저장소에서 보실 수 있습니다.[7]

LocalSettings.php

마지막으로 LocalSettings.php가 요청마다 토큰 파일을 읽어 렌더러 주소 뒤에 붙이게 합니다. $chartRendererUrl에는 모듈의 url 출력값을 넣습니다. $wgChartServiceUrl이 비어 있으면 차트 확장 기능은 위키 서버에서 node를 실행하려 들기 때문에, 토큰 파일이 없을 때도 주소는 정해 두어야 합니다.

wfLoadExtension( 'Chart' );
// 모듈의 url 출력값
$chartRendererUrl = 'https://….lambda-url.ap-northeast-2.on.aws/';
$chartRendererToken = '/run/secrets/chart-renderer-token';
$wgChartServiceUrl = $chartRendererUrl . ( is_readable( $chartRendererToken )
	? trim( file_get_contents( $chartRendererToken ) )
	: '' );

차트 확장 기능은 JsonConfig 확장 기능으로 Data: 이름공간의 표 데이터(.tab)와 차트 정의(.chart)를 읽으니, JsonConfig를 설치한 뒤 두 콘텐츠 모델을 직접 등록해 주셔야 합니다.[8]

wfLoadExtension( 'JsonConfig' );
$wgJsonConfigModels['Tabular.JsonConfig'] = 'JsonConfig\JCTabularContent';
$wgJsonConfigs['Tabular.JsonConfig'] = [
	'namespace' => 486,
	'nsName' => 'Data',
	'pattern' => '/.\.tab$/',
	'license' => 'CC-BY-SA-4.0',
	'isLocal' => true,
];
$wgJsonConfigModels['Chart.JsonConfig'] = 'MediaWiki\Extension\Chart\JCChartContent';
$wgJsonConfigs['Chart.JsonConfig'] = [
	'namespace' => 486,
	'nsName' => 'Data',
	'pattern' => '/.\.chart$/',
	'license' => 'CC-BY-SA-4.0',
	'isLocal' => true,
];

설정을 마친 뒤 Data: 이름공간에 표 데이터(.tab)와 차트 정의(.chart) 문서를 만들고 아무 문서에 {{#chart:예시.chart}}를 넣어 보면 차트가 나오는지 확인하실 수 있습니다.

운영해 보니

페미위키에서는 설치하고 사흘 동안 함수가 180번 불렸고 오류나 제한에 걸린 요청은 없었습니다. 이 속도면 한 달 요청 1,700건쯤, 실행 시간 670GB초쯤인데, 프리 티어를 빼고 계산해도 한 달에 0.01달러 정도입니다. 다만 한동안 불리지 않다가 처음 그리는 차트는 함수가 새로 뜨고 공개키를 받아 오느라 1~4초쯤 걸립니다.

맺음말

모듈은 MIT 라이선스로 공개되어 있습니다. 아직 1.0이 되지 않아 다음 버전에서 입력값이 바뀔 수 있으니, 예시처럼 version으로 버전을 고정해 쓰시기를 권합니다. 발급자를 STS로 묶어 두지 않았기 때문에 위키 서버가 AWS 밖에 있더라도 OpenID Connect 토큰을 발급받을 수 있다면 쓰실 수 있습니다. 쓰시다가 문제가 생기면 저장소에 이슈를 남겨 주세요.

출처

  1. ↑ “T334940 Graph extension disabled”. 《Wikimedia Phabricator》 (영어). 2026년 11월 7일에 확인함. 
  2. ↑ “Extension:Chart”. 《MediaWiki》 (영어). 2026년 11월 7일에 확인함. 
  3. ↑ “wmf-config/CommonSettings.php - operations/mediawiki-config”. 《Wikimedia Gerrit》 (영어). 2026년 11월 7일에 확인함. 
  4. ↑ “feat(chart-renderer): render charts for MediaWiki's Chart extension · Pull Request #230 · femiwiki/lambda”. 《GitHub》 (영어). 2026년 11월 8일에 확인함. 
  5. ↑ “GetWebIdentityToken”. 《AWS Security Token Service API Reference》 (영어). 2026년 11월 7일에 확인함. 
  6. ↑ 6.0 6.1 “femiwiki/terraform-aws-mediawiki-chart-renderer”. 《GitHub》 (영어). 2026년 11월 7일에 확인함. 
  7. ↑ “feat(femiwiki): enable charts drawn by the chart-renderer Lambda · Pull Request #1733 · femiwiki/docker-mediawiki”. 《GitHub》 (영어). 2026년 11월 7일에 확인함. 
  8. ↑ “README.md - mediawiki/extensions/Chart”. 《Wikimedia Gerrit》 (영어). 2026년 11월 8일에 확인함.