AWS Lambda上でGCCを動かす。


はじめに

オンラインジャッジをサーバーレスで構築してみたかったので、まずLambda上でGCCを動くようにしました。

環境

Node.js 12.x

GCCが動作するのに必要なもの

  • GCC: 本体。
  • Binutils: GASとか。
  • GLibC: ヘッダーファイルやランタイムなど。

普通の環境ではGCCのみコンパイルすれば動かすことができます。
ですがLambdaの中には最小限のソフトウェアしかインストールされていないため、いくつか別にインストールする必要があります。

インストール先

最初に思いつくであろう方法がプログラムを含めたZIPファイルに一緒に含めてGCCをアップロードする方法です。
ただその場合ZIPファイルの容量制限がかなり厳しいので、結構機能制限しないとGCCをアップロードすることはできません。
またGCCだけで容量がいっぱいいっぱいになるため、他のコンパイラをインストールすることができないというデメリットもあります。
そのため今回はGCCをインストールしたEFSを用意して、LambdaにマウントすることでGCCを動作可能にすることとしました。

インストール方法

EFSに書き込むにはEC2を使うのが手軽なため、EC2でコンパイルとインストールを行いました。
またt2.microではメモリが足りなかったため、今回はt2.smallを使っています。

1. EFSの作成

Amazon Management Consoleもしくは、aws-cliを用いて作成して下さい。

2. EC2へEFSをマウント

以下のコマンドを実行します。

sudo mount -t efs -o tls,iam ファイルシステムID マウントポイント

ここからはEFSを/mnt/compilers/にマウントして、gccを/mnt/compilers/usrにインストールするものとして進めます。

3. コンパイルするためにEC2にGCCなどをインストールする

何をインストールしたか記録をとるのを忘れていました。
頑張って探してみてね。

4. Makeのインストール

EC2(Amazon Linux)のMakeはバージョンが古く、Binutilsをmakeするときに怒られるので新しいバージョンをインストールします。

cd
wget http://ftp.jaist.ac.jp/pub/GNU/make/make-4.3.tar.gz
tar -xf make-4.3.tar.gz
mkdir build-make
cd build-make
../make-4.3/configure
make
sudo make install

5. GCCのインストール

cd
git clone git://gcc.gnu.org/git/gcc.git
mkdir build-gcc
cd build-gcc
../gcc/configure \
    --prefix=/mnt/compilers/usr \
    --with-local-prefix=/mnt/compilers/usr \
    --enable-languages=c++ \
    --disable-shared \
    --disable-lto \
    --disable-gcov \
    --disable-threads \
    --disable-bootstrap \
    --disable-multilib
make
sudo make install

6. BinUtilsのインストール

cd
git clone git://sourceware.org/git/binutils-gdb.git
mkdir build-binutils
cd build-binutils
../binutils-gdb/configure --prefix=/mnt/compilers/usr
make
sudo make install

7. GLibCのインストール

cd
git clone git://sourceware.org/git/glibc.git
mkdir build-glibc
cd build-glibc
../glibc/configure --prefix=/mnt/compilers/usr
make
sudo make install

AWS Lambdaにマウント

EFSをLambdaにマウントするためにはEFS側にアクセスポイントを作成する必要があります。
また、Lambda側もVPC Lambdaに変更して下さい。
そうすると、LambdaにEFSをマウントすることができるようになりますのでEC2と同じパス(今回は/mnt/compilers)にEFSをマウントしてください。
なお、VPC Lambdaにはいろいろと制限があることに注意して下さい。

動作確認

テストコード
const { promisify } = require('util');
const fs = require('fs');
const child_process = require('child_process');

const exec = promisify(child_process.exec);

exports.handler = async (event, context) => {
    fs.writeFileSync("/tmp/Main.c", "#include<stdio.h>\nint main() { puts(\"Hello world\"); }");
    return await exec('/mnt/compilers/usr/bin/gcc /tmp/Main.c -o /tmp/a.out && /tmp/a.out');
};

実行すると…

Response
{
  "stdout": "Hello world\n",
  "stderr": ""
}

おわりに

これを使ってサーバーレスでオンラインジャッジを組んでコンテストサイトを作ろうと思っています。