ํฌ๋กค๋ง์ ์คํํ๋ ๋ฐฉ๋ฒ์ ๋ค์ํ์ง๋ง, ๊ทธ ์ค์์๋ Docker + ECR + Lambda
์กฐํฉ์ ์ ํํ ๋ฐ์๋ ๋ช
ํํ ์ด์ ๊ฐ ์์ต๋๋ค.
ํ๋ฃจ์ ํ ๋ฒ๋ง ๋์์ํฌ ์์ ์ด์๊ณ , ๋์ ์๊ฐ์ 1์๊ฐ ๋ด์ธ๋ก ๋ง๋ฌด๋ฆฌ ๋ ์ ์๋ ์์
์
๋๋ค.
EC2
- ์์ ์๋ฒ ์ด์์ด ๊ฐ๋ฅํ๋ ๋น์ฉ ๋ถ๋ด์ด ํผLambda + EventBridge
- ์ ํด์ง ์๊ฐ์๋ง ๋ฆฌ์์ค ์ฌ์ฉ โ ๋น์ฉ ํจ์จ์ ์ด๊ณ ๊ด๋ฆฌ ๊ฐํธ๐
Lambda + EventBridge
์กฐํฉ์ด ์ต์ํ์ ๋น์ฉ์ผ๋ก ์์ ์ ์ธ ํฌ๋กค๋ง ์์ ์ํ ๊ฐ๋ฅ!
โ
Docker + ECR์ ์ ํํ ์ด์
Lambda์์ ์ฌ์ฉํ๋ Layer๋ ์์ถ ๊ธฐ์ค 50MB, ์์ถ ํด์ ์ 250MB์ ์ฉ๋ ์ ํ์ด ์์ต๋๋ค.
๊ทธ๋ฌ๋ ์ ํฌ๊ฐ ์ฌ์ฉํ๋ Chrome ๋๋ผ์ด๋ฒ๋ 1GB ์ด์์ ์ฉ๋์ด ํ์ํ์ฌ Lambda Layer๋ก๋ ํ์ฌ๊ฐ ๋ถ๊ฐ๋ฅํ์ต๋๋ค.
๋ฐ๋ฉด, Docker Image๋ ์ต๋ 10GB๊น์ง ํ์ฉ๋์ด Lambda์์๋ ๋์ฉ๋ ํฌ๋กค๋ง ํ๊ฒฝ ๊ตฌ์ฑ์ด ๊ฐ๋ฅํ์ฌ ์ด ๋ฐฉ๋ฒ์ผ๋ก ์ ํํ์ต๋๋ค.
๐ ์ฝ๋ ์ ์ฒด๋ ์๋ Github ์ ์ฅ์ ์ฐธ๊ณ
๐ lambda-selenium-docker ๊ตฌ์ฑ ํ์ผ ๋ณด๊ธฐ
/opt/
๊ฒฝ๋ก์ ์ค์น/var/task/
์ ์์นํด์ผ Lambda์์ ์ธ์ ๊ฐ๋ฅPython ๋ฒ์ | ์ด์์ฒด์ | ํจํค์ง ๊ด๋ฆฌ์ | ํน์ง |
---|---|---|---|
3.9 ~ 3.11 | Amazon Linux 2 | yum | ๊ธฐ์กด ์ค์น ๋ฐฉ์๊ณผ ์ ์ฌ |
3.12 ~ 3.13 | Amazon Linux 2023 | dnf | yum ๋ฏธ์ง์ โ dnf ์ฌ์ฉ ํ์, ํจํค์ง ์์ด |
chrome-deps.txt
: chrome ์ค์น์ฉ ํจํค์ง ๋ฆฌ์คํธinstall-browser.sh
: ํฌ๋กฌ ๋ฐ ๋๋ผ์ด๋ฒ ์ค์น ์คํฌ๋ฆฝํธrequirements.txt
: pip ํจํค์ง ๋ชฉ๋กcrawler.py
: Selenium ์คํ ์ฝ๋์ฐ์ 2๋ฒ์ ๋์์๋ Github์ ์๋ ํ์ผ์ ๋ฐ์, crawler.py
ํ์ผ์ ๊ธฐํธ์ ๋ง๊ฒ ์์ ํ์๊ณ ์งํํด ์ฃผ์๋ฉด ๋ฉ๋๋ค.
Docker ๋น๋ ์ linux/x86_64
์ต์
์ ์ถ๊ฐํด ์ฃผ์ด์ผ ๋๋ค ๋ด์์ ํฌ๋กฌ์ด ์ ์์ ์ผ๋ก ๋์ํฉ๋๋ค.
์ ๋ crawler
๋ผ๋ ์ด๋ฆ์ผ๋ก ์์ฑ๋๋๋ก ๋น๋๋ฅผ ์งํํ์ต๋๋ค.
$ docker build --platform linux/x86_64 -t crawler -f Dockerfile .
๋ง์ฝ ๊ธฐ๋ฅ ์
๋ฐ์ดํธ๋ฅผ ํ์์๋ Docker Image๊ฐ ๋์ผํ๊ฒ ๋น๋๋๋ค๋ฉด --no-cache
์ต์
์ ์ถ๊ฐํด ์ฌ์๋ ํด์ฃผ์๋ฉด ๋ฉ๋๋ค.
$ docker build --no-cache --platform linux/x86_64 -t crawler -f Dockerfile .
AWS ํํ์ด์ง์์ IAM ๊ณ์ ์ ์์ฑํด ์ฃผ๋ฉด, access_key, secret_key
์ ๋ณด๋ฅผ ๋ฐ๊ธ๋ฐ์ ์ ์์ต๋๋ค.
์ด๋ ๊ฒ ์์ฑํ IAM ๊ณ์ ์ ๋ณด๋ฅผ ๋ก์ปฌ์์๋ ์ด์ฉํ ์ ์๋๋ก aws configure
๋ช
๋ น์ด๋ก ์ธํ
ํด ์ค๋๋ค.
# Access Key, Secret Key, ๋ฆฌ์ ๋ฑ ์
๋ ฅ
$ aws configure
AWS Access Key ID [None]:
AWS Secret Access Key [None]:
Default region name [None]:
Default output format [None]:
์ด์ ์์ ์ ๋ ฅํ IAM ๊ณ์ ์ ๊ธฐ๋ฐ์ผ๋ก ๋ด ์ ๋ณด๊ฐ ์ ์์ ์ผ๋ก ๋ถ๋ฌ์์ง๋์ง ์ฒดํฌํด ์ค๋๋ค.
$ export ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
$ echo "export ACCOUNT_ID=${ACCOUNT_ID}" | tee -a ~/.bash_profile
e.g) export ACCOUNT_ID=905418424719
ACCOUNT_ID
๊ฐ ๋์จ๋ค๋ฉด ์ ์์ ์ผ๋ก ์ฒ๋ฆฌ๋ ๊ฒ์
๋๋ค.
๊ทธ๋ฌ๋ฉด ~/.bash_profile
์ ์ ์ฅ๋์ด ํฐ๋ฏธ๋์ ์๋ก ์ฐ ๊ฒฝ์ฐ์๋ ACCOUNT_ID
๊ฐ ์๋์ผ๋ก ํ์ธ๋ฉ๋๋ค.
AWS ECR ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ์์ฑํ๋ฉด ๋ฆฌํฌ์งํ ๋ฆฌ ์ด๋ฆ, URI, ์์ฑ ๋ ์ง
๋ฑ์ ์ ๋ณด๋ค์ ํ์ธํ ์ ์๋๋ฐ, ECR Push
๋๋ Lambda
์ ๋ฑ๋กํ๊ธฐ ์ํด์๋ URI ์ ๋ณด๊ฐ ํ์ํฉ๋๋ค.
lambda-crawler-docker
$ACCOUNT_ID.dkr.ecr."๋ฆฌ์ ๋ช
".amazonaws.com/"๋ฆฌํฌ์งํ ๋ฆฌ ์ด๋ฆ"
3๋ฒ ํญ๋ชฉ์์ ์ค์ ํ Docker Image ์ด๋ฆ์ ํ์ธํด ์ค๋๋ค.
์ ๊ฐ ์ค์ ํ Docker image ์ด๋ฆ์ crawler
์
๋๋ค.
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
crawler latest 179972fa2472 5 hours ago 1.75GB
crawler
๋ผ๋ ๋ก์ปฌ Docker Image๋ฅผ 905418424719.dkr.ecr.ap-northeast-2.amazonaws.com/lambda-crawler-docker
๋ผ๋ ์ด๋ฆ์ผ๋ก ์ฌํ๊น
ํ์ต๋๋ค.
๊ทธ๋ฌ๋ฉด ์ด์ ์ด ์ด๋ฏธ์ง๋ AWS ECR์ ํธ์ํ ์ค๋น๊ฐ ์๋ฃ๋ ์ํ์
๋๋ค.
$ docker tag crawler 905418424719.dkr.ecr.ap-northeast-2.amazonaws.com/lambda-crawler-docker
๊ทธ๋ฌ๋ฉด ๋์ผํ IMAGE ID
๋ฅผ ๊ฐ์ง ์ด๋ฏธ์ง๊ฐ ๋ ๊ฐ์ REPOSITORY
์ด๋ฆ์ผ๋ก ๋ณด์ฌ์ง๋๋ค.
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
crawler latest 179972fa2472 5 hours ago 1.75GB
905418424719.dkr.ecr.ap-northeast-2.amazonaws.com/lambda-crawler-docker latest 179972fa2472 5 hours ago 1.75GB
Docker Client๋ฅผ ์ธ์ฆํ์ฌ ์ฌ์ฉํ๋๋ก ๋ก๊ทธ์ธํ๋ ๋จ๊ณ์ ๋๋ค.
$ aws ecr get-login-password --region "ap-northeast-2" | docker login --username AWS --password-stdin 905418424719.dkr.ecr.ap-northeast-2.amazonaws.com/lambda-crawler-docker
์ ๋ช
๋ น์ด๋ฅผ ์
๋ ฅํ์ ๋ ์๋์ ๊ฐ์ด ๊ถํ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.
์ด๋ ๊ฒ ๋ฐ์ํ ์ด์ ๋ IAM ๊ณ์ ์ ์๋ฌด๋ฐ ๊ถํ์ ๋ถ์ฌํ์ง ์์๊ธฐ ๋๋ฌธ์ ๋ฐ์ํ ๊ฒ์
๋๋ค.
ecr:GetAuthorizationToken
๊ถํ์ docker login
ํ ๋ ECR
์์ ์ธ์ฆ ํ ํฐ์ ๋ฐ๊ธ๋ฐ๊ธฐ ์ํด ํ์ํ ๊ถํ์
๋๋ค.
์ฆ, ECR
๋ก๊ทธ์ธ ์์ฒด๋ฅผ ํ ์ ์๋ ์ํฉ์
๋๋ค.
An error occurred (AccessDeniedException) when calling the GetAuthorizationToken operation: User: arn:aws:iam::905418424719:user/ecr_iam is not authorized to perform: ecr:GetAuthorizationToken on resource: * because no identity-based policy allows the ecr:GetAuthorizationToken action
Error: Cannot perform an interactive login from a non TTY device
์ด์ ์ ์์ฑํ IAM ๊ณ์ ์ ์ ์ํ์ฌ ์๋ ๊ถํ์ ์ถ๊ฐํด ์ค๋๋ค.
Login, Image Push, Pull, Repository ๋ณด๊ธฐ
๋ฑ์ ํ๊ธฐ ์ํ ๊ถํ์
๋๋ค.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:GetRepositoryPolicy",
"ecr:DescribeRepositories",
"ecr:ListImages",
"ecr:BatchGetImage",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload"
],
"Resource": "*"
}
]
}
๊ถํ ๋ถ์ฌ๊ฐ ์๋ฃ๋์๋ค๋ฉด ์ด์ ๋ ์๋ ๋ช ๋ น์ด ์ ๋ ฅ ์ ์ฑ๊ณต์ ์ผ๋ก ๋ก๊ทธ์ธ์ด ๋์ค๊ฒ๋๋ค.
$ aws ecr get-login-password --region "ap-northeast-2" | docker login --username AWS --password-stdin 905418424719.dkr.ecr.ap-northeast-2.amazonaws.com/lambda-crawler-docker
Login Succeeded
๋ํ ๊ถํ ๋ถ์ฌ๊ฐ ์๋ฃ๋์๋ค๋ฉด Push ๋ช ๋ น๋ ์ฑ๊ณต์ ์ผ๋ก ๋์ค๊ฒ๋๋ค.
# ๊ฐ์ ECR URI๋ก ๋ณ๊ฒฝํด ์ฃผ์
์ผ ํฉ๋๋ค.
$ docker push 905418424719.dkr.ecr.ap-northeast-2.amazonaws.com/lambda-crawler-docker
Using default tag: latest
The push refers to repository [905418424719.dkr.ecr.ap-northeast-2.amazonaws.com/lambda-crawler-docker]
5f70bf18a086: Pushed
ebacb58d496a: Pushed
f310bac807bc: Pushed
7616ce0b8029: Pushed
1b16eae0ab65: Pushed
b5a6332d1ae3: Pushed
aff961962f64: Pushed
feeb3f35672c: Pushed
22b878155803: Pushed
b07d597978b2: Pushed
13a69907ade8: Pushed
6a9b57324378: Pushed
bdd4095bce79: Pushed
608834524d4b: Pushed
f4b46dc2d7e0: Pushed
latest: digest: sha256:a7cbc7cf9844e0347236962623ce83f95dcddf350a90932ac8db3e173cfdba5b size: 3469
4๋ฒ ํญ๋ชฉ์์ ์
๋ก๋ํ ECR URI
๋ฅผ ๊ฐ์ง๊ณ Lambda
ํจ์๋ฅผ ์์ฑํด ์ค๋๋ค.
Lambda ํจ์๋ ์ต๋ 15๋ถ ๋์ ๊ฐ๋ฅํฉ๋๋ค.
๋ค๋ง, ์ฒ์ ์์ฑ ์ ์ด๊ธฐ ์ธํ
์ 3์ด๋ก ๋์ด ์๊ณ ๋ฉ๋ชจ๋ฆฌ์ ์์ ์คํ ๋ฆฌ์ง ํ ๋น๋๋ ๋ฎ์ ๋ณ๊ฒฝ์ด ํ์ํฉ๋๋ค.
์ ๋ ์ ํ์๊ฐ 14๋ถ 59์ด
, ๋ฉ๋ชจ๋ฆฌ 2048MB
, ์์ ์คํ ๋ฆฌ์ง 5120MB
๋ก ๋ณ๊ฒฝํ์ต๋๋ค.
ํ ์คํธ ํญ์์ ํ ์คํธ ๋ฒํผ์ ํด๋ฆญํ๋ฉด ์งํ ๊ฐ๋ฅํฉ๋๋ค.
ํ์ง๋ง ์ ๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.
Chrome
์คํ ๊ณผ์ ์ค์ crashed
๊ฐ ๋ฐ์ํ๋ค๋ ๋ด์ฉ์ด์๊ณ ์ถ์ ๋๋ ์์ธ์ ํ๋์ฉ ํ์
ํด ๊ฐ๋ฉฐ ํ
์คํธ๋ฅผ ๋ฐ๋ณตํ์ต๋๋ค.
{
"errorMessage": "Message: unknown error: Chrome failed to start: crashed.\n (chrome not reachable)\n (The process started from chrome location /opt/chrome/chrome is no longer running, so ChromeDriver is assuming that Chrome has crashed.)\nStacktrace:\n#0 0x55a053875759 <unknown>\n#1 0x55a05380ecf3 <unknown>\n#2 0x55a0535eddc3 <unknown>\n#3 0x55a0536125ed <unknown>\n#4 0x55a05360db03 <unknown>\n#5 0x55a053647779 <unknown>\n#6 0x55a053641ef3 <unknown>\n#7 0x55a05361827b <unknown>\n#8 0x55a053619455 <unknown>\n#9 0x55a05383d870 <unknown>\n#10 0x55a05384f8b0 <unknown>\n#11 0x55a05384f5bc <unknown>\n#12 0x55a05384fe32 <unknown>\n#13 0x55a05383eb9b <unknown>\n#14 0x55a0538500b6 <unknown>\n#15 0x55a0538304dd <unknown>\n#16 0x55a053867888 <unknown>\n#17 0x55a053867a12 <unknown>\n#18 0x55a053881c2e <unknown>\n#19 0x7f353e0ae44b <unknown>\n#20 0x7f353cbc152f <unknown>\n",
"errorType": "WebDriverException",
"requestId": "599f599b-7765-4bf5-9f79-efa5c5436a5e",
"stackTrace": [
" File \"/var/task/crawler.py\", line 9, in handler\n suc_cnt, err_cnt = crawler_target()\n",
" File \"/var/task/crawler.py\", line 33, in crawler_target\n driver = webdriver.Chrome(service=service, options=chrome_options)\n",
" File \"/var/lang/lib/python3.9/site-packages/selenium/webdriver/chrome/webdriver.py\", line 45, in __init__\n super().__init__(\n",
" File \"/var/lang/lib/python3.9/site-packages/selenium/webdriver/chromium/webdriver.py\", line 66, in __init__\n super().__init__(command_executor=executor, options=options)\n",
" File \"/var/lang/lib/python3.9/site-packages/selenium/webdriver/remote/webdriver.py\", line 212, in __init__\n self.start_session(capabilities)\n",
" File \"/var/lang/lib/python3.9/site-packages/selenium/webdriver/remote/webdriver.py\", line 299, in start_session\n response = self.execute(Command.NEW_SESSION, caps)[\"value\"]\n",
" File \"/var/lang/lib/python3.9/site-packages/selenium/webdriver/remote/webdriver.py\", line 354, in execute\n self.error_handler.check_response(response)\n",
" File \"/var/lang/lib/python3.9/site-packages/selenium/webdriver/remote/errorhandler.py\", line 229, in check_response\n raise exception_class(message, screen, stacktrace)\n"
]
}
ํฌ๋กค๋งํ๋ ํ์ด์ฌ ํ์ผ ๋ด ์ต์
์ ์ง์ ํ ์ ์๋ ๋ฐ์ด๋๋ฆฌ ํ์ผ ์์น๋ฅผ ๋๋ฝํ์ต๋๋ค.
๊ทธ๋์ ์ฐพ์ ์ ์์๊ณ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.
chrome_options.binary_location = "/opt/chrome/chrome"
1๋ฒ ๋ด์ฉ์ ์์ ํ ํ, ํ
์คํธ๋ฅผ ์งํํ์ง๋ง ๋ ๋ค์ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.
headless
๋ฅผ ๋น๋กฏํ no-sandbox
, disable-dev-shm-usage
์ต์
์ ๋๋ฝํ์ต๋๋ค..ใ
ใ
๋งฅ์์ ํ
์คํธํ ๋๋ ํ๋ฉด์ ๋ณด๋ฉฐ ํ
์คํธํด์ ์๋ ์ต์
๋ค์ ์ ๊ฑฐํ์๋๋ฐ,
Lambda์์๋ GUI๊ฐ ์๋ ์๋ฒ ํ๊ฒฝ์ด์ด์ ํด๋น ์ต์
์ด ์์ผ๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
chrome_options.add_argument('--headless')
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
์ต์ ์ค๋ช
--headless
- ๋ธ๋ผ์ฐ์ UI ์์ด ๋ฐฑ๊ทธ๋ผ์ด๋์์ Chrome์ ์คํํฉ๋๋ค.--no-sandbox
- Chrome์ ๋ณด์ ์๋๋ฐ์ค๋ฅผ ๋นํ์ฑํํฉ๋๋ค.--disable-dev-shm-usage
- /dev/shm
(๊ณต์ ๋ฉ๋ชจ๋ฆฌ)๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ๋์ ๋์คํฌ ๊ธฐ๋ฐ์ ์์ ์ ์ฅ์(/tmp
)๋ฅผ ์ฌ์ฉํ๋๋ก ์ค์ ํฉ๋๋ค.๋ฐ์ดํฐ๊ฐ ์ ์ ํ์ด์ง ๊ธฐ์ค์ผ๋ก ํ ์คํธ๋ฅผ ์งํํ์๋๋ฐ, ์ฑ๊ณต์ ์ผ๋ก ์ฒ๋ฆฌ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ๋์ด๋~
์ฐธ๊ณ ์๋ฃ ๐ฉ