Serverless PHP (2019년 말)

Laravel Vapor가 유명세를 떨치고 있는 와중에, 국내에서는 상대적으로 덜 주목받는 프로젝트를 소개합니다.

AWS

작년 12월 php-annotated-monthly에 Lambda Layers를 소개드린 적이 있습니다.

AWS re:Invent 2018 conference에서 발표된 내용에 따르면 AWS Lambda에서도 PHP를 쓰기 위해 우회하지 않아도 정식으로 쓸 수 있게 됐습니다.

Lambda Runtime API를 사용하면 되는데, stackery/php-lambda-layer(AWS SAM 기반. 현재는 활발히 운영되는 저장소가 아닙니다)를 통해 만들거나 직접 만들 수도 있다고 소개 드렸습니다.

이후 AWS 서버리스 환경에서 PHP를 실행하기 위한 많은 툴이 등장했고, Google App Engine에는 PHP 7이 표준 환경으로 포함되어 있습니다. 이들 환경에서 Hello World PHP 코드 하나 실행하는 것은 큰 어려움이 없으므로 각 환경에서 Laravel Framework으로 실행하는 방법이나 정리해보고자 글을 시작했습니다. (그것 또한 너무 간단합니다만..)

AWS Lambda에는 PHP built-in support가 없으므로 Lambda layers를 통해 3rd-party runtime을 필요로 합니다. 앞으로 소개할 Serverless PHP에 도움이 되는 프로젝트는 Layers라고 불리는 PHP용 runtime을 제공합니다.

Bref

https://bref.sh/docs/

Serverless(“The complete solution for building & operating serverless applications”)라는 프레임웍을 활용해 PHP 응용 프로그램을 간단하게 실행하는 것을 목표로 합니다.

그렇다면 이미 Serverless 만으로도 PHP 어플리케이션을 띄우는 것이 가능하다는 사실.

https://serverless.com/examples/

여기서 PHP나 Java로 검색하면 결과가 하나씩 나옵니다. 상대적으로 관심이 덜한 언어라고 해야할까요?

어쨌든 간단한 PHP 예제인 serverless/examples 페이지로 들어가 봅시다. serverless.yml을 열어보면 Apache OpenWhisk가 제공하는 PHP runtime을 활용하는 것을 볼 수 있습니다.

AWS Lambda에 쉽게 올리는 PHP 코드와 같은 식의 글은 대부분 이런 식입니다. 결국 AWS에서는 누군가가 만들어 놓은 runtime이 필요하구나…생각하시면 되겠습니다. 물론 직접 만들 수도 있습니다.

AWS의 블로그에 PHP runtime을 만드는 좋은 글이 있긴 하지만, PHP 7.3 기반의 runtime을 만들 수 있는 방법을 소개하는 글도 있습니다.

  • Lambda Execution Environment의 기초가 되는 AMI를 여기서 찾아 인스턴스를 띄우고, Lambda function을 관리할 수 있는 IAM Role을 부여한 후, ssh로 접근해서 PHP를 컴파일 합니다. 그리고 나서 이리저리 요래오래 막 하면 됩니다…

위에서 언급한 Apache OpenWhisk는 PHP를 정식으로 지원하는데요. 더 확인하고 싶으시면 아래를 참고하시고요.

다시 Bref

다시 Bref 이야기를 해보죠. 갑자기 기분이 이상해서 제가 5월에 공유한 php-annotated-monthly을 뒤져봤습니다. Bref는 분명 AWS SAM을 기반으로 했다고 써있는 겁니다.

사실 그동안 Serverless로 가느냐 SAM으로 가느냐 여러 논의가 있었더랬습니다. Serverless는 AWS에 종속되지 않으므로 확장성 면에서 더 유리하기 때문이가 싶었지만, Bref는 어차피 AWS만 지원하는데…

이슈를 좀 뒤져보면 커뮤니티 내부에서도 좀 혼란스러웠던 것 같습니다.

https://github.com/brefphp/bref/issues/99

  • Serverless와 SAM 비교 (뭐 하나가 특별히 우월하진 않은 모양입니다)

https://github.com/brefphp/bref/issues/320

  • “Mainly: it supports layers and it can run things locally with Docker”라고 이유를 밝혔습니다.

Bref를 활용한 또 다른 사례 연구들을 찾아봐도 혼란스럽습니다.

https://blog.deleu.dev/deploying-laravel-artisan-on-aws-lambda/

  • SAM 활용

https://blog.servmask.com/serverless-for-php-developers/

  • Serverless 활용

어쨌든 아직까지는 공식적으로 Serverless를 사용합니다.

참고로 Taylor Otwell이 Vapor를 발표할 때, Bref와의 차이가 뭐냐는 질문에 아래와 같이 답변했었습니다.

Yes a bit different… the whole setup is custom and not based on SAM or Serverless Framework… queue and web are separate lambdas… we have a few PHP extensions that are not on Bref runtime such as GD

진짜 Bref 이야기

공식 문서에 소개된 글을 읽어보죠.

Bref(Brief를 프랑스어로 한 것)는 Composer 패키지로 제공되며 PHP 애플리케이션을 AWS에 배포 하고 AWS Lambda 에서 실행할 수 있도록 도와줍니다

1
2
3
4
5
Why Bref?
* Bref는 PHP 응용 프로그램을 간단하게 실행하는 것을 목표로합니다
* (모든 요구 사항을 해결하는 대신) 선택을 줄여 문제를 단순화
* (강력한 맞춤형 솔루션을 목표로하는 대신) 간단하고 친숙한 솔루션 제공
* (불완전한 추상화 뒤로 너무 많은 것을 숨기는 대신) 지식을 공유하여 힘을 실어 준다

대충 뭔가 쉽다는 이야기입니다. (잘 하려면 어렵다는 이야기이기도…)

1
2
3
4
5
Bref는 다음을 제공합니다.
* 문서
* AWS Lambda 용 PHP 런타임
* 배포 툴링
* PHP 프레임 워크 통합

이 정도는 뭐 당연히 제공해야할 것으로 보이긴 합니다.

Bref를 통해 구현할 수 있는 것은

1
2
3
4
5
Use cases
* APIs
* workers
* batch processes/scripts
* websites

이런 다양한 용도로 사용할 수 있다고 하고요. 구현 사례에 가보시면 더 많은 예제를 보실 수 있습니다.

최근에는 externals.io(PHP internals 소식을 외부로 가져오는 서비스)가 Bref로 LAMP에서 Serverless로 성공적으로 옮겨왔다는 소식을 전해드리기도 했었죠. 꽤 유익한 글입니다.

From LAMP to serverless: case study of externals.io

여기에는 Serverless로 올렸을 때의 비용을 계산해주는 기능도 소개해줍니다.

(사실 external.io는 Bref를 운영하는 null이란 회사에서 운영 비용을 지원해주고 있습니다. )

계속 공식 문서를 보자면,

maturity-matrix에 언급하기로 Legacy application이나 Jobs/Cron의 난이도(문서나 가격면에서)를 제외하면 꽤 자신있는 모습입니다.

Bref로 Lambda 실행하기

우선 공식 문서의 GETTING STARTED 중 InstallationFirst steps 그리고 Deployment guide를 따라하면 Lambda에서 실행할 준비가 됩니다.

이런 식입니다. 프로젝트를 만들고,

1
2
composer require bref/bref
vendor/bin/bref init

여기서 주의깊게 봐야할 것이 serverless.yml 파일입니다. Serverless framework에서 제공하는 간단한 설정 파일인데, tutorial을 따라하시게 되면 아마 region 정도 바꿔주시면 될 겁니다.

1
2
composer install --optimize-autoloader --no-dev
serverless deploy

이 단계에서 CloudFormation stack으로 배포되는데, 아래와 같은 서비스들을 묶어주는 것 뿐입니다.

  • lambda functions
  • S3 buckets
  • databases

이런 전체 스택이 한번에 뜨고, 지울 땐 같이 지워집니다.

몇 분 후 배포가 되면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
❯ serverless deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service app.zip file to S3 (3.4 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.................................
Serverless: Stack update finished...
Service Information
service: app
stage: dev
region: us-east-1
stack: app-dev
resources: 11
api keys:
None
endpoints:
ANY - https://**********.execute-api.us-east-1.amazonaws.com/dev
ANY - https://**********.execute-api.us-east-1.amazonaws.com/dev/{proxy+}
functions:
api: app-dev-api
layers:
None
Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.

위에 노출된 endpoint로 실행 결과를 확인하실 수 있습니다.

모니터링도 해볼까요?

1
vendor/bin/bref dashboard

Bref로 Laravel 띄우기

공식 문서에선 Laravel이나 Symphony 프로젝트를 띄울 수 있는 방법을 안내해줍니다.

https://bref.sh/docs/frameworks/laravel.html

우선 Laravel 프로젝트를 만들어 보고요.

1
composer create-project --prefer-dist laravel/laravel breftest

serverless.yml을 생성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
service: bref-demo-laravel

provider:
name: aws
region: us-east-1
runtime: provided
environment:
# Laravel environment variables
APP_STORAGE: '/tmp'

plugins:
- ./vendor/bref/bref

functions:
website:
handler: public/index.php
timeout: 28 # in seconds (API Gateway has a timeout of 29 seconds)
layers:
- ${bref:layer.php-73-fpm}
events:
- http: 'ANY /'
- http: 'ANY /{proxy+}'
artisan:
handler: artisan
timeout: 120 # in seconds
layers:
- ${bref:layer.php-73} # PHP
- ${bref:layer.console} # The "console" layer

데모 프로제트 저장소의 serverless.yml를 살펴보아도 좋습니다.

Laravel과 같은 프레임워크를 띄우기 위해선 몇가지 소스 수정이 되어야 합니다(이 역시 공식 문서에 잘 설명되어 있습니다).

Lambda는 /tmp를 제외하고는 read-only이므로 compile된 view 파일을 담기 위해 path 설정을 해줘야 합니다.

bootstrap/app.php 파일에서 $app = new Illuminate\Foundation\Application 다음에 아래와 같이 수정해줍니다.

1
2
3
4
5
6
7
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
/*
* Allow overriding the storage path in production using an environment variable.
*/
$app->useStoragePath($_ENV['APP_STORAGE'] ?? $app->storagePath());

.env에는 아래 설정이 필요합니다.

1
2
3
4
5
6
7
VIEW_COMPILED_PATH=/tmp/storage/framework/views 

# We cannot store sessions to disk: if you don't need sessions (e.g. API) # then use `array`, else store sessions in database or cookies
SESSION_DRIVER=array

# Logging to stderr allows the logs to end up in Cloudwatch
LOG_CHANNEL=stderr

마지막으로 app/Providers/AppServiceProvider.php에서 view 파일용 디렉토리를 생성해줍니다.

1
2
3
4
5
6
7
public function boot()
{
// Make sure the directory for compiled views exist
if (! is_dir(config('view.compiled'))) {
mkdir(config('view.compiled'), 0755, true);
}
}

배포하기 전 config cache 파일은 지워줍니다. path가 달라져서 문제가 생길 수 있어요.

1
php artisan config:clear

Google App Engine

Google App Engine에는 PHP 7이 표준 환경으로 포함되어 있습니다.

Google App Engine > 공식 문서

공식 문서에 Laravel을 올리는 tutorial도 있습니다.

크게 어렵지 않은데요.

app.yaml을 생성합니다.

1
2
3
4
5
6
7
8
runtime: php72

env_variables:
## Put production environment variables here.
APP_KEY: YOUR_APP_KEY
APP_STORAGE: /tmp
VIEW_COMPILED_PATH: /tmp
SESSION_DRIVER: cookie

YOUR_APP_KEY 부분에는 application key를 생성해 넣어야죠.

1
php artisan key:generate --show

GAE에서도 /tmp만 쓸 수 있는 것은 동일해서 해야할 것은 크게 다르지 않습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
# [START] Add the following block to `bootstrap/app.php`
/*
|--------------------------------------------------------------------------
| Set Storage Path
|--------------------------------------------------------------------------
|
| This script allows you to override the default storage location used by
| the application. You may set the APP_STORAGE environment variable
| in your .env file, if not set the default location will be used
|
*/
$app->useStoragePath(env('APP_STORAGE', base_path() . '/storage'));
# [END]

캐싱 문제를 해결하기 위해 composer dependency를 제거합니다.

1
composer remove --dev beyondcode/laravel-dump-server

배포하면,

1
gcloud app deploy

짠!

Firevel

독특한 프레임웍을 하나 더 소개해드립니다.

역시 이전에 php-annotated-monthly에서 소개됐던 글입니다.

Serverless PHP on App Engine + Cloud Firestore and Firevel.

Google App Engine에서 잘 실행될 수 있게, Laravel을 수정해 Firevel이라는 프레임웍을 만들었다고 합니다. Firestore를 DB와 캐시로 사용합니다. Eloquent가 NoSQL을 고려하지 않았기 때문에 이에 대응하는 Firequent라는 패키지도 개발했다고 합니다.

README에 나온 간단한 Installation 정도면 쉽게 서비스를 띄울 수 있습니다. 저는 로컬 환경에서 생소한 gRPC extension 같은 걸 설치하면서 삽질을 좀 했는데, 환경만 준비되면 서버 띄우는 건 쉽습니다.

그러나 이미 Laravel에 익숙하신 분들에겐 Firequent 등 너무 많은 신지식을 필요로 하기 때문에, ‘굳이 저런 걸…’이라는 생각이 들 수 밖에 없지만 흥미롭긴 합니다. (흥미롭고 안 써야지)

정리

고작 Laravel 기본 페이지를 띄워 본 정도라서 얼마나 Laravel의 경험을 그대로 이어갈 수 있을지는 모르겠습니다. Production 레벨로 올리기에는 아마도 삽질을 많이 해야겠지만, 잘 쓰면 편하고 저렴한 비용으로 서비스를 할 수도 있겠습니다.

잠깐. 저렴하다고요?

Serverless: slower and more expensive | Hacker News

Serverless: 15% slower and 8x more expensive - Einar Egilsson

The cost of serverless - Faun - Medium

The hidden costs of serverless - Amiram Shachar - Medium

가벼운 페이지에 사람 손을 덜 타는 페이지라면 도전해볼만 하지 않겠습니까?