Skip to main content

· 6 min read

error: externally-managed-environment

Homebrew를 사용하여 설치한 Python 3.12를 새로 사용하게 되었다. Homebrew는 $(brew --prefix)/lib/pythonX.Y/site-packages 경로에 패키지를 설치하기 때문에, 기존에 사용하던 여러 패키지(module)들을 다시 설치해야 했다. 아무 생각없이 pip install sshtunnel로 패키지 설치를 시도하였지만, 아래와 같은 에러를 보게 되었다.

> pip3 install sshtunnel
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try brew install
xyz, where xyz is the package you are trying to
install.

If you wish to install a non-brew-packaged Python package,
create a virtual environment using python3 -m venv path/to/venv.
Then use path/to/venv/bin/python and path/to/venv/bin/pip.

If you wish to install a non-brew packaged Python application,
it may be easiest to use pipx install xyz, which will manage a
virtual environment for you. Make sure you have pipx installed.

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.

웬 처음보는 에러가 나오는데, --break-system-packages라는, 딱 봐도 별로 쓰고싶지 않은 flag를 써서 문제를 우회할 수 있다는 내용이 있었다.

이를 적용하기에는 무언가 찜찜했기 때문에, 결국 hint에 언급되는 PEP 668에 대해 찾아보게 되었다.

PEP 668과 Homebrew Python@3.12

Python-specific 패키지 관리 도구들(pip 등)은 기본적으로 전역 환경에 패키지들을 설치해왔으나, 이는 distro-installed 패키지 관리 도구(brew 등)와 충돌을 유발하면서 여러 문제를 유발하였다. 이에 Python-specific 패키지 관리 도구는 전역 환경에 패키지를 설치/제거하는 것을 지양하고 가상 환경에만 패키지를 관리할 것을 권장하는 것이 PEP 688이 제안하는 내용이다.

내가 여태까지 안일하게 사용하던, 프로젝트별 가상환경 없이 pip로 $(brew --prefix)/lib/pythonX.Y/site-packages경로에 패키지를 설치하는 방식에 문제가 있었던 것은 사실이다. 패키지 이름, 버전 충돌 등 여러 예상치 못한 문제가 발생할 수 있기 때문에, 프로덕션 환경에 적합한 방식은 절대 아니다. 하지만 이 PEP는 2021년에 제안된 것으로, 갑자기 이제 와서 문제를 발생시키는 것이 의아했다. Python 3.11까지는 저런 에러가 발생한 적이 없었는데, 하필 3.12부터 이를 적용하게 된 걸까?

검색을 거듭하여, Homebrew Python 문서에서 답을 찾을 수 있었다.

Starting with Python@3.12, Homebrew follows PEP 668.

정말로 Homebrew는 3.12부터 PEP 668을 따르게 된 것이었다. 공식 문서에서 제안하는 Python 패키지를 설치하는 방식은 아래 2가지였다.

  • python 3 -m venv path/to/venv 로 가상환경을 생성하고 path/to/venv/bin/python, path/to/venv/bin/pip를 사용하는 방식
  • pipx install xyz와 같이 pipx를 사용하는 방식

venv야말로 정석적인 해결방식이겠지만, pipx는 또 뭘까?

pipx

pipx는 pip와 같이 Python 패키지를 설치/관리하는 도구이다. 하지만 pip가 환경에 구애받지 않고 라이브러리/애플리케이션을 설치하는 범용 패키지 관리자인 반면, pipx는 애플리케이션 단위의 설치와 실행에 초점을 맞추기 때문에 대상 애플리케이션과 그 연관 패키지들만을 위한 고립된 환경을 생성한다는 차이점이 있다. 즉 pipx는 이미 완성된 Python 애플리케이션을 설치하고 실행하는 도구이므로, 프로젝트별 개발 환경을 세팅하는 단계에서 venv의 대체재가 될 수 있는 도구는 아닌 것으로 보인다.

결론

Python3.12부터(Homebrew 기준) PEP 668을 도입함에 따라 --break-system-packages 플래그를 사용해야만 전역 환경에 패키지를 설치할 수 있도록 변경되었다. 그간 가상환경 구분 따위 없이 default 경로에(즉, 전역 환경에) 마구잡이로 패키지를 설치하여 사용해왔으나, 이를 계기로 프로젝트별 venv를 사용함으로써 보다 안정적인 Python 실행 환경을 구축하고자 한다.

References

PEP 688 관련

Homebrew 문서

pipx

· 5 min read

Amazon CodeGuru

Amazon CodeGuru는 코드를 넣으면 코드를 분석하고 결과를 출력하는 서비스이다. 특히 AWS API를 사용하는 코드를 분석할 때 뛰어난 성능을 보여준다고 한다. CodeGuru 서비스는 CodeGuru Reviewer와 Profiler로 나뉘는데, Reviewer는 안정성(보안 취약점 식별, Best practice 제안), Profiler는 퍼포먼스에 초점을 둔 서비스로 볼 수 있다. 내가 관심있는 기능은 Reviewer이기 때문에, 이번 포스트에서는 Reviewer에 한정하여 작성한다.

CodeGuru로는 Java와 Python 코드를 분석할 수 있으며, AWS CodeCommit, Bitbucket, GitHub, GitHub Enterprise Server의 리포지토리는 Reviewer와 연동하여 사용이 가능하다. 문제는 우리 팀은 리모트 리포지토리로 GitLab을 사용하고 있어서 리포지토리와 직접 연동이 어렵다는 점이다. 하지만 해결책은 있기 마련이어서, GitLab에서 제공하는 Repository mirroring 기능을 활용하면 GitLab 리포지토리와 AWS CodeCommit 리포지토리를 연동할 수 있고, AWS 블로그에서 CodeGuru Reviewer CLI를 사용하여 CI/CD Pipeline을 설정하는 방법도 찾을 수 있었다.

GitLab Repository Mirroring(AWS Codecommit)

AWS CodeCommit 리포지토리는 AWS 루트 계정에 종속된 자원이므로, 아래와 같이 IAM을 통한 권한 설정이 선행되어야 한다.

  1. IAM User에 AWSCodeCommitPowerUser 정책을 붙인다.
  2. 해당 User의 Security credentials에 가서 HTTPS Git credentials for AWS CodeCommit에서 Git credential을 생성한다.

이후 CodeCommit 리포지토리를 생성하고 그 URL을 복사한다. 주의할 점은 CodeGuru는 2022년 9월 현재 Seoul 리전에서 서비스되지 않기 때문에 CodeGuru가 목적일 경우 다른 리전에 생성하여야 한다.

CodeCommit 리포지토리의 URL과 CodeCommitPowerUser 정책이 붙은 IAM User의 Git credential 2가지 정보가 있다면 비로소 GitLab에서 Reporitory mirroring 기능을 설정할 수 있다.

GitLab 리포지토리의 Setting > Repository > Mirroring repositories에서 URL을 입력하는데, 앞서 언급한 2개의 정보를 조합하여야 한다. CodeCommmit 리포지토리 URL은 https://git-codecommit.REGION_NAME.amazonaws.com/v1/repos/REPOSITORY_NAME 형태인데, HTTP로 Git을 사용하기 위해 프로토콜 뒤에 Git Credential의 username을 추가하고 @를 추가해야 한다. 즉, 아래와 같은 형태로 URL을 입력한다 https://GIT_CREDENTIAL_USERNAME@git-codecommit.REGION_NAME.amazonaws.com/v1/repos/REPOSITORY_NAME 이후 아래 필드에 Password를 입력하고 저장하면 GitLab 리포지토리와 CodeCommit 리포지토리 간 Mirroring 관계가 성립된다.

CodeGuru Reviewer CLI

고민했지만 AWS 블로그의 자세한 설명 이상으로 좋은 설명을 쓸 자신은 없어서 링크로 대신하기로 했다.

https://aws.amazon.com/blogs/devops/automating-detection-of-security-vulnerabilities-and-bugs-in-ci-cd-pipelines-using-amazon-codeguru-reviewer-cli/

Jenkins 인스턴스에 CodeGuru Reviewer CLI(AWS CLI 아님)를 설치하고 권한을 설정하는 것이 주요 내용으로, CLI를 통해 지정한 위치에 output 파일이 생성되어 분석 결과를 확인하거나 결과값에 따라 파이프라인 분기를 설정하는 방식이다. 다만 CLI를 사용할경우 CodeGuru Console에서는 관련 내용을 확인할 수 없다는 단점이 있다.

출처

https://docs.aws.amazon.com/codeguru/latest/reviewer-ug/welcome.html https://docs.aws.amazon.com/codeguru/latest/profiler-ug/what-is-codeguru-profiler.html https://docs.gitlab.com/ee/user/project/repository/mirror/ https://docs.aws.amazon.com/codecommit/latest/userguide/setting-up-gc.html?icmpid=docs_acc_console_connect https://aws.amazon.com/blogs/devops/automating-detection-of-security-vulnerabilities-and-bugs-in-ci-cd-pipelines-using-amazon-codeguru-reviewer-cli/

· 5 min read

Web Application Firewall

AWS에서 제공하는 서비스인 WAF는 Web Application Firewall의 약자이지만, WAF라는 용어는 AWS에서 새로 만든 것은 아니다. 따라서 WAF, 그 이전에 Firewall이 무엇인지 아주아주 간략하게나마 이해할 필요가 있다.

Firewall(이하 방화벽)은 일반 인터넷과 내부 네트워크를 분리하는 HW/SW의 조합으로, 패킷을 허용 또는 차단하는 방식으로 동작한다. 최초의 방화벽은 패킷의 정보(IP 주소, 포트 번호, ACK 비트 등)와 정해진 정책에 따라 패킷을 필터링하는 기능을 수행하였으나, 점차 다양한 방향으로 발전하였다.

이 중 애플리케이션이나 서비스에 적용되는 L7 트래픽을 제어하는 방화벽을 Appplication Firewall이라고 불린다. 따라서, WAF는 웹 애플리케이션에 적용되는 Application Firewall을 말한다.

AWS WAF

AWS WAF는 규칙 기반(Rule-based)으로 특정 AWS resource의 인/아웃바운드 트래픽을 제어할 수 있는 서비스이다.

WAF로 제어 가능한 AWS resource는 아래와 같다.

  • Amazon CloudFront distribution
  • Amazon API Gateway REST API
  • Application Load Balancer
  • AWS AppSync GraphQL API responds to HTTP(S) web requests

Web ACL, Rule, Rule Group

WAF는 일종의 규칙 목록인 Web ACL을 관리할 resource에 연결하는 방식으로 동작한다. Web ACL은 여러 개의 Rule을 가질 수 있으며, 사용자는 다양한 Statement를 조합하여 Rule이 원하는 대로 동작하도록 구성할 수 있다. 또, 여러 개의 Rule을 Rule Group이라는 resource로 묶어서 관리할 수 있는데, Web ACL 역시 Rule들의 집합이라 볼 수 있으므로 Rule Group과 Web ACL의 관계는 처음 이해할 때 개인적으로 굉장히 헷갈리는 부분이었다.

AWS resource에 연결된 Web ACL은 WAF 관점에서 해당 resource를 추상화한 인터페이스로 볼 수 있다. 즉, 어떤 Web ACL에 어떤 규칙을 추가하는 것은 곧 그 Web ACL이 연결된 AWS resource에 그 규칙을 적용하는 것과 같다.

반면, Rule Group은 함께 사용되면 편리한 Rule들을 한 번에 적용하기 위한 관리 단위로, Rule Group을 AWS resource에 적용하기 위해서는 Web ACL에 이를 'Rule Group을 참조하는 Rule' 형태로 추가하여 사용한다.

다만 Web ACL과 AWS resource의 대응 관계는 1:N으로, 하나의 resource에는 하나의 Web ACL만 연결할 수 있지만, 하나의 Web ACL을 여러 resource에 연결하여 재사용하는 것은 가능하다. (단, CloudFront Distribution에 연결된 Web ACL은 다른 종류의 resource들에 연결할 수 없다.)

Web ACL Capacity Unit(WCU)

모든 Rule은 전부 그 복잡도에 따라 사용하는 컴퓨팅 파워의 양을 나타내는 척도를 갖는데, 이를 WCU라 한다. 각 Web ACL은 1500의 WCU 상한선(Support 요청을 통한 상향 조정은 가능)을 가지므로, 이를 고려하여 resource 관리 체계를 구성하여야 한다.

Rule Group을 최초 생성 시 변경 불가능한 WCU 상한선(Capacity)을 지정하여야 하는데, Rule Group이 갖는 Rule들의 WCU 총합(Used Capacity)은 (당연하게도) Capacity를 초과할 수 없다.

Web ACL에 Rule Group을 추가하게 되면 Rule Group의 Capacity(Used Capacity 아님)가 Web ACL의 WCU 사용량을 잡아먹는다. 따라서 Rule Group의 Capacity를 필요 이상으로 크게 생성하면 WCU 상한선을 넘겨서 Rule Group을 다시 생성하여야 하므로, 적정 수준의 Capacity를 설정하는 것이 바람직하다.

출처

컴퓨터 네트워킹: 하향식 접근 - 제6판 (Kurose, Ross / Pearson / 2012) https://en.wikipedia.org/wiki/Firewall_(computing) https://docs.aws.amazon.com/waf/latest/developerguide/what-is-aws-waf.html

· 2 min read

scp

scp는 Secure Copy의 약어로, 원격 호스트에 파일을 전송하는 수단이다. SSH를 사용하기 때문에 통신 탈취 등으로부터 안전하다.

rsync

rsync는 Remote Sync의 약어로, 마찬가지로 원격 호스트에 파일을 전송하는 수단이다. 하지만 두 파일 간 차이(difference)만을 전달하는 알고리즘을 사용하기 때문에, 대부분의 경우에 보다 효율적인 파일 전송이 가능하다. 또, SCP에 비해 훨씬 다양한 옵션을 제공하기 때문에 이를 활용하여 보다 다양한 작업을 수행할 수 있다.

하지만 rsync가 scp에 비해 반드시 우월하다고는 볼 수 없는데, rsync 자체는 암호화되지 않은 평문으로 데이터를 전송하기 때문에, 보안성을 위해서 SSH를 사용한 암호화를 사용(--rsh=ssh 옵션을 추가)하여야 한다. 반면 scp는 항상 SSH 기반 통신을 수행하기 때문에 단순하거나 가벼운 작업을 실행할 경우에는 scp를 사용하여 파일을 전송하는 것이 편리하다.

출처

https://stackoverflow.com/questions/20244585/how-does-scp-differ-fromrsync- https://madplay.github.io/post/scprsync-

· 3 min read

SSH Port Forwarding과 그 종류

  • SSH Tunneling이라고도 부름
  • 특정 포트를 다른 연결에 forwarding하는 행위
  • 프록시와 유사한 역할을 수행
  • Local, Remote, Dynamic 포트 포워딩이 있음

Local Port Forwarding

Local port forwarding은 말 그대로 localhost(SSH 클라이언트)의 특정 포트를 다른 연결로 forward한다.

ssh -L <local port>:<target server>:<target port> <username>@<remote server>
  • <local port>: SSH 터널링의 시작점으로 사용될 SSH 클라이언트가 동작하는 호스트의 포트
  • <target server>: 최종 목적지 서버
  • <target port>: <target server>의 포트
  • <remote server>: SSH 터널링을 통해 도달하는, Gateway(Bastion) 역할을 하는 서버(SSH 서버)

즉, 클라이언트 환경에서 위와 같은 프로세스가 실행되면, localhost:<local port>로 전달되는 요청은 username@<remote server>에서 <target server>:<target port>에 보내지는 요청으로 처리된다.

ssh -L 123:localhost:456 root@1.2.3.4

이미지 출처: https://unix.stackexchange.com/questions/115897/whats-ssh-port-forwarding-and-whats-the-difference-between-ssh-local-and-remot

위 예시는 target server를 localhost, 즉 자기 자신(gateway server, SSH server, 1.2.3.4)으로 지정한 경우이다. 이 때는 접근이 허용된 어느 호스트(자기 자신을 포함하여)에서든 프로세스를 실행한 클라이언트의 123번 포트(아래 이미지의 your host:123)로 요청을 보내면, 해당 요청은 ssh 터널을 통해 root@1.2.3.4 --> 127.0.0.1:456 연결로 forward된다.

ssh -L 123:<farawayhost>:456 root@1.2.3.4

이미지 출처: https://unix.stackexchange.com/questions/115897/whats-ssh-port-forwarding-and-whats-the-difference-between-ssh-local-and-remot

반면 위와 같이 target server를 다른 farawayhost로 지정하여 포트 포워딩을 실행할 경우, 클라이언트의 123번 포트로 보내진 요청은 ssh 터널을 통해 1.2.3.4 --> farawayhost:456으로 forward된다.

즉, Local Port Forwarding은 아래와 같은 기능을 수행한다.

<some_host> --> <client_host>:<client_port> 를 아래 연결로 forward한다. <remote_host> --> <target_host>:<target_port>

Local Port Forwarding의 이점

Local port forwarding을 통해, 불특정 다수(<some_host>)의 <target_host>:<target_port>로의 접근 제어를 <client_host>:<client_port>로의 접근 제어를 통해 관리할 수 있다. 즉 <target_host>:<target_port>는 <remote_host>(Bation Host의 역할을 수행)로부터의 접근만 허용하면 된다는 점에서 <target_host>를 보다 안전하게 관리할 수 있고, 보안/시스템적 제약 사항을 우회하는 데에도 자주 쓰인다. 컨테이너 기반 환경에서는 특히 이러한 Local Port Forwarding을 요긴하게 사용할 수 있다.

출처

https://linux.systemv.pe.kr/ssh-%ED%8F%AC%ED%8A%B8-%ED%8F%AC%EC%9B%8C%EB%94%A9/ https://blog.naver.com/PostView.naver?blogId=alice_k106&logNo=221364560794 https://www.hanbit.co.kr/network/category/category_view.html?cms_code=CMS5064906327 https://unix.stackexchange.com/questions/115897/whats-ssh-port-forwarding-and-whats-the-difference-between-ssh-local-and-remot https://www.ssh.com/acadeclient/ssh/tunneling/example

· 8 min read

Stateless에게 State를

HTTP는 무상태(stateless) 프로토콜이다. 하지만 클라이언트와의 상호작용을 위해서는 서버가 클라이언트(사용자)의 상태(state)를 알 수 있어야 한다. 이를 위해 등장한 기술이 몇 종류 존재하는데, 쿠키, 세션, 웹 스토리지가 그것이다.

쿠키(Cookie)

쿠키는 서버가 클라이언트를 식별하고 클라이언트의 상태를 저장하기 위해 사용하는, 클라이언트에 대한 정보를 저장한 파일이다. 브라우저는 서버의 응답으로부터 쿠키를 전달받아 클라이언트 환경에 key-value 형태로 저장하였다가, 추후 HTTP 요청 시 해당 도메인으로부터 등록된 모든 쿠키를 요청과 함께 전달한다.

클라이언트 입장에서, 쿠키에 자신의 정보가 저장되는 것은 탈취당할 요소를 추가하는 것이므로 보안적인 위협이 된다.
반면, 쿠키를 관리/전달하는 주체는 클라이언트이므로 클라이언트 쪽에서는 이를 얼마든지 위/변조하여 서버에게 전달할 수 있고, 따라서 서버 입장에서도 클라이언트가 전달하는 쿠키가 보안적 위험 요소로 작용할 수 있다.

위와 같은 단점이 존재함에도 불구하고, 쿠키는 여전히 널리 사용된다. 서버가 클라이언트를 식별하고 클라이언트의 상태에 따라 동작하기 위해서는 클라이언트의 정보가 반드시 어딘가에는 저장되어 있어야 하므로, 이를 위한 가장 간편한 해결책인 쿠키는 보안에 민감한 정보가 아닐 경우에 한해 가장 먼저 고려할 수 있는 수단이다.

세션(Session)

위에서 쿠키 방식의 단점 중 하나는, 클라이언트 쪽에서 위/변조 또는 탈취한 쿠키를 사용할 경우, 서버 쪽에서 이를 탐지하고 해당 요청을 거부할 수 없다는 것이었다. 따라서, 그런 중요한 정보를 클라이언트 쪽에서 전달하는 대로 믿지 않고 서버에서 직접 사용자의 정보를 관리하는 해결책을 떠올릴 수 있고, 이를 일반적으로 세션 방식이라고 부른다. 다만 앞서 설명한 것처럼 HTTP는 stateless 프로토콜이므로 서버에서 일방적으로 클라이언트를 식별할 수 있는 방법은 없다. 따라서, 서버에 필요한 정보를 저장하여도 해당 정보와 그 주인(특정 클라이언트)를 매칭시키기 위해서는 클라이언트를 식별하기 위한 식별자 정보가 필요하고, 이를 위해 서버는 클라이언트 식별자로 사용할 수 있는 쿠키를 발급한다.

쿠키 방식세션 방식이라고 흔히들 말하는 것에 반해, 엄밀히 따지자면 쿠키와 세션은 대립되는 선택지가 아니다. 세션 방식에서도 쿠키를 사용하기 때문이다. (쿠키를 사용하지 않는 경우도 있다고 하나, 쿠키를 사용하여 세션을 관리하는 방식이 가장 널리 쓰인다고 한다.) 하지만 세션을 사용한 사용자의 인증은 중요한 정보가 서버 측에 저장되어 불특정 클라이언트로부터의 위/변조가 어렵고, 클라이언트의 정보가 탈취당한다 하여도 세션이 만료된다면, 즉 클라이언트의 브라우저가 종료되거나 서버 쪽에서 설정한 세션 시간이 경과되면 해당 값은 사용할 수 없는 값이 되어버리므로 비교적 안전하다.

물론 브라우저가 종료되면 사용자의 정보는 모두 사라지는 점은 때로는 단점으로 작용하여, 이를 유지하기 위해 쿠키를 사용하여야 할 때도 있다. 또 대규모 시스템의 경우, 가용성을 확보하기 위해 여러 대의 서버를 사용하여 부하를 분산하게 되는데, 이러한 경우 한 클라이언트가 보내는 연속적인 요청을 한 서버가 맡아서 처리하지 않으면 사용자가 시스템을 정상적으로 사용할 수 없으므로, 세션 정보만을 처리하는 별도의 서버를 분리하여 운영하기도 한다.

웹 스토리지(Web Storage)

웹 스토리지는 비교적 최근에 등장한 기술로 HTML5 표준에서 정의되었으며, 쿠키와 유사하면서도 장/단점이 존재하지만 쿠키의 단점을 보완해줄 수 있는 기술이다. 웹 스토리지는 만료 시간이 없는 로컬 스토리지와 탭 단위 휘발성을 갖는 세션 스토리지로 구분되어 데이터의 용도에 따라 저장 장소를 선택하여 데이터의 생애 주기를 보다 효과적으로 관리할 수 있어 보다 안전하다. 또, 존재하는 모든 정보가 매번 무조건 전송되는 쿠키와 달리, 웹 스토리지에 있는 데이터 중 어떤 데이터를 전송할 지 선택할 수 있으며, string값만 저장할 수 있었던 쿠키와 달리 Javascript 객체를 저장할 수 있게 되었다는 점도 보다 다양한 방식으로 HTTP를 사용할 수 있는 여지를 준다.

출처

https://kamang-it.tistory.com/entry/Web%EC%A1%B0%EA%B8%88-%EB%8D%94-%EC%9E%90%EC%84%B8%ED%9E%88%EB%8F%84%EB%8C%80%EC%B2%B4-%EC%99%9C-%EC%9D%B4%EB%A6%84%EC%9D%B4-%EC%BF%A0%ED%82%A4%EC%9D%B8%EA%B1%B8%EA%B9%8C-%EC%83%81%ED%83%9C%EB%A5%BC-%EC%A0%80%EC%9E%A5%ED%95%98%EB%8A%94-http-cookie https://kamang-it.tistory.com/entry/Web%EC%A1%B0%EA%B8%88-%EB%8D%94-%EC%9E%90%EC%84%B8%ED%9E%88%EC%84%9C%EB%B2%84%EC%99%80-%ED%81%B4%EB%9D%BC%EC%9D%98-%EC%97%B0%EA%B2%B0%EA%B3%A0%EB%A6%AC-%EC%83%81%ED%83%9C%EB%A5%BC-%EC%84%9C%EB%B2%84%EC%97%90-%EC%A0%80%EC%9E%A5%ED%95%98%EB%8A%94-http-session-cookie%EC%99%80%EC%9D%98-%EB%B9%84%EA%B5%90 https://kamang-it.tistory.com/entry/Web%EC%A1%B0%EA%B8%88-%EB%8D%94-%EC%9E%90%EC%84%B8%ED%9E%88cookie%EB%8A%94-%EB%84%88%EB%AC%B4-%EA%B5%AC%EC%8B%9D%EC%95%84%EB%83%90-%EC%9D%B4%EC%A0%9C%EB%B6%80%ED%84%B4-Web-Storage https://kcizzang.tistory.com/entry/SessionStorage-%EC%99%80-LocalStorage-%EC%B0%A8%EC%9D%B4%EC%A0%90

· 4 min read

Package

패키지는 바이너리 실행 파일들과 그 메타데이터(configuration 파일, dependency 정보)의 모음이며, 일반적으로 DEB(데비안 계열), RPM(레드햇 계열) 등 다양한 아카이브 파일 형식으로 배포된다. 패키지 매니저는 아카이브 파일을 열고 지정된 위치에 바이너리 실행 파일을 위치시키고, 그 파일이 어떤 패키지에 속하는지 파악해 두었다가 패키지를 삭제할 때 이를 삭제한다.

또, 패키지 매니저는 아래와 같은 기능을 수행하여 패키지 관리를 용이하게 해준다.

  1. 패키지 의존성 관리: 패키지 간 의존 관계를 분석하여 어떤 패키지를 설치/제거할 경우 해당 패키지가 의존하는 다른 패키지들 역시 설치/제거하여 패키지 간 의존 관계를 사용자가 직접 관리하지 않아도 효과적으로 패키지를 사용할 수 있게 한다.
  2. 패키지 버전 관리: 설치된 패키지들 중 리포지토리에 사용 가능한 업데이트가 있는 패키지가 있는지 확인하고 필요 시 업데이트하는 역할도 수행한다.

Repository

리눅스 환경에서, 패키지 매니저가 설치 대상 패키지를 가져오는 가장 보편적인 방법은 리포지토리(여기서의 리포지토리란 소프트웨어 리포지토리로, git이나 SVN같은 VCS에서 사용하는 리포지토리와는 다른 개념이다.)에서 원하는 패키지를 가져오는 방식이다. 각 리눅스 배포판마다 실행이 가능한 패키지를 제공하는 리포지토리를 운영하므로, 이를 활용하면 사용자는 편리하게 환경에 맞는 패키지를 설치/관리할 수 있다. 만약 리포지토리에서 제공하지 않는 소프트웨어를 설치하여야 할 경우, 사용자는 패키지 매니저라는 툴이 존재하지 않던 시절처럼 직접 자신의 환경에서 실행이 가능하도록 소스 파일을 빌드하고 환경을 구성하여야 한다. 여기서 말하는 환경 구성은 소프트웨어 의존 요소의 관리, 바이너리 파일의 위치 지정 등을 포함하며, 이에 추가로 구성 스크립트나 makefile등을 사용하도록 설계된 소프트웨어도 있을 것이다. 이는 굉장히 번거로운 일이며, 설치한 패키지가 많을수록 그 관리의 복잡성은 증가하고 문제 발생의 여지도 크다.

출처

https://itsfoss.com/package-manager/ https://en.wikipedia.org/wiki/Software_repository https://en.wikipedia.org/wiki/Package_manager https://www.howtogeek.com/117579/htg-explains-how-software-installation-package-managers-work-on-linux/ https://help.ubuntu.com/community/Repositories/Ubuntu

· 13 min read

#74 errored

오랜만에 블로그에 포스트를 작성하고 블로그를 둘러보니, 이전에 작성한 불필요한 포스트가 보였다. 이를 로컬 디렉토리에서 삭제한 후, 블로그에 반영하기 위해 리포지토리 main 브랜치에 push하였다. 리포지토리는 Travis CI를 통해 빌드/배포 자동화가 설정되어 있으므로, 여느 때와 같이 빌드/배포 작업이 진행될거라 기대했지만, 빌드가 실패했다는 메일을 받게 되었다. 지난 11월에 블로그 프레임워크를 Hexo로 바꾸면서, Hexo 튜토리얼 문서를 보고 그대로 따라한 뒤(https://bastionsofwill.github.io/2021/11/17/hexo-travis-ghpage) 처음 겪는 빌드 실패여서 당혹스러웠으나, 몇 시간 전에 성공한 것이 실패한 이유는 당연히 안 해본 짓(포스트 삭제)을 했기 때문일 것으로 추정하고 해결을 미뤘다. 하지만 빌드 실패 결과를 살펴본 결과, 문제는 다른 곳에 있음을 알 수 있었다.

Job Log

Worker information
(생략)
Build system information
(생략)
$ git clone --depth=50 --branch=main https://github.com/bastionsofwill/bastionsofwill.github.io.git bastionsofwill/bastionsofwill.github.io

Setting environment variables from repository settings
$ export GH_TOKEN=[secure]

$ nvm install 16

Setting up build cache
$ export CASHER_DIR=${TRAVIS_HOME}/.casher
$ Installing caching utilities
attempting to download cache archive
fetching main/cache--linux-xenial-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855--node-16.tgz
found cache
$ node --version
v16.15.1
$ npm --version
8.11.0
$ nvm --version
0.39.1
$ yarn --version
1.22.18

$ yarn --frozen-lockfile
hexo generate
(생략)
The command "hexo generate" exited with 0.

store build cache
changes detected (content changed, file is created, or file is deleted):\n/home/travis/.npm/_logs/2022-06-09T17_42_37_173Z-debug-0.log\n
changes detected, packing new archive
uploading main/cache--linux-xenial-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855--node-16.tgz
cache uploaded

$ rvm $(travis_internal_ruby) --fuzzy do ruby -S gem install dpl
Installing deploy dependencies
ERROR: Error installing dpl-pages:
The last version of multipart-post (>= 1.2, < 3) to support your Ruby & RubyGems was 2.2.0. Try installing it with `gem install multipart-post -v 2.2.0` and then running the current command again
multipart-post requires Ruby version >= 2.6.0. The current ruby version is 2.4.5.335.
Successfully installed public_suffix-3.0.3
Successfully installed addressable-2.8.0
/home/travis/.rvm/rubies/ruby-2.4.5/lib/ruby/site_ruby/2.4.0/rubygems/core_ext/kernel_require.rb:54:in `require': cannot load such file -- dpl/provider/pages (LoadError)
from /home/travis/.rvm/rubies/ruby-2.4.5/lib/ruby/site_ruby/2.4.0/rubygems/core_ext/kernel_require.rb:54:in `require'
from /home/travis/.rvm/gems/ruby-2.4.5/gems/dpl-1.10.16/lib/dpl/provider.rb:93:in `rescue in block in new'
from /home/travis/.rvm/gems/ruby-2.4.5/gems/dpl-1.10.16/lib/dpl/provider.rb:68:in `block in new'
from /home/travis/.rvm/gems/ruby-2.4.5/gems/dpl-1.10.16/lib/dpl/cli.rb:41:in `fold'
from /home/travis/.rvm/gems/ruby-2.4.5/gems/dpl-1.10.16/lib/dpl/provider.rb:67:in `new'
from /home/travis/.rvm/gems/ruby-2.4.5/gems/dpl-1.10.16/lib/dpl/cli.rb:31:in `run'
from /home/travis/.rvm/gems/ruby-2.4.5/gems/dpl-1.10.16/lib/dpl/cli.rb:7:in `run'
from /home/travis/.rvm/gems/ruby-2.4.5/gems/dpl-1.10.16/bin/dpl:5:in `<top (required)>'
from /home/travis/.rvm/gems/ruby-2.4.5/bin/dpl:23:in `load'
from /home/travis/.rvm/gems/ruby-2.4.5/bin/dpl:23:in `<main>'
failed to deploy

에러가 난 부분을 보면, deploy dependencies를 설치하다가 dpl-pages에서 에러가 발생했고, multipart-post가 Ruby 2.6.0을 요구하는데 현재 버전이 2.4.5.335인 것이 문제로 보인다.

errored?

먼저 Travis CI가 동작하는 과정에 대한 간략한 이해가 필요하다. Travis CI가 수행할 작업을 정의하는 구조는 phase -> job -> stage -> build로, 하나의 job은 아래와 같은 phase로 구성된다.

  1. before_install
  2. install: dependency의 설치
  3. before_script
  4. script: build script 실행
  5. before_cache(OPTIONAL)
  6. after_success/failure
  7. before_deploy(OPTIONAL)
  8. deploy(OPTIONAL)
  9. after_deploy(OPTIONAL)
  10. after_script

stage는 이러한 job들이 병렬로(in parellel) 실행되는 그룹을 말하며, build는 이러한 stage들이 순차적으로 실행되는 그룹을 말한다.

하나 이상의 job이 실패할 경우 그 job이 속한 빌드는 비정상(break)이 되며, 비정상 빌드는 아래 세 가지 키워드 중 하나로 표시된다.

  • errored: before_install, install, before_script, before_deploy phase가 정상적으로 종료되지 않을 경우(non-zero exit code) 발생
  • failed: script phase가 정상적으로 종료되지 않을 경우 발생
  • canceled: 사용자에 의한 취소 after_success/failure, after_deploy, after_script의 exit code는 빌드 결과에 영향을 미치지 않지만, 타임아웃이 발생했을 경우 빌드 결과는 failed로 표시된다.

즉, 위의 job log를 살펴본 결과, hexo generate라는 1줄짜리 스크립트가 실행되는 script phase까지는 무사히 실행되었으나, before_deploy phase에서 deploy dependency를 설치하다가 오류가 나서 build가 errored로 표시된 것임을 확인할 수 있었다.

dpl-pages와 multipart-post

에러가 난 지점에 대한 감은 잡을 수 있었다. 하지만 여전히 문제에 대한 지식은 많이 모자란 상황이다.

Installing deploy dependencies
ERROR: Error installing dpl-pages:
The last version of multipart-post (>= 1.2, < 3) to support your Ruby & RubyGems was 2.2.0. Try installing it with `gem install multipart-post -v 2.2.0` and then running the current command again
multipart-post requires Ruby version >= 2.6.0. The current ruby version is 2.4.5.335.

에러 로그를 다시 살펴보면, dpl-pages를 설치하다가 에러가 발생하였고, multipart-post가 요구하는 ruby 버전이 2.6.0 이상인 것이 문제임을 알 수 있었다.

첫번째로 든 의문은, 'dpl-pages, multipart-post는 또 뭔데?'였다. '배포 작업을 수행하기 위해 필요한 무언가' 정도로만 추정이 되는 상황이었으므로, Travis CI가 배포하는 방식에 대한 지식이 필요했다.

이전에 생각없이 hexo tutorial을 복붙하면서, 나는 인지하지 못한 채 Travis CI의 GitHub Pages Deployment 기능을 사용하고 있었다.

deploy:
provider: pages
skip-cleanup: true
github-token: $GH_TOKEN
keep-history: true
on:
branch: main
local-dir: public

위의 yml 파일이 관련 설정으로, provider: pages를 비롯한 설정을 통해 hexo generate의 결과물인 public 디렉토리 하위 파일들을 타겟 브랜치(지정하지 않았으므로 기본값인 gh-pages 브랜치)에 push하는 기능이다.

해당 기능에 대한 Travis CI 문서 확인 결과, dpl이라는 루비 코드를 사용하는 것을 알 수 있었다. 하지만 여기서 결국 dpl-pages, multipart-post에 대한 내용은 결국 찾지 못했는데, ruby에 대한 지식이 없는 상황에서 dpl의 문서에는 관련 내용이 없기 때문이다. 하지만 dpl은 rubygem으로 publish/관리되고 있다는 내용이 있었으므로, dpl, dpl-pages, multipart-post라는 gem에 대한 기본적인 정보는 찾을 수 있었다.

갈림길

Installing deploy dependencies
ERROR: Error installing dpl-pages:
The last version of multipart-post (>= 1.2, < 3) to support your Ruby & RubyGems was 2.2.0. Try installing it with `gem install multipart-post -v 2.2.0` and then running the current command again
multipart-post requires Ruby version >= 2.6.0. The current ruby version is 2.4.5.335.

위의 메시지를 자세히 보면, 배경이 어떻든 결국 multipart-post라는 gem이 ruby 2.6.0 이상을 요구하는 반면, 환경에 설치된 ruby는 2.4.5.335 버전인 것이 문제임을 알 수 있다.

따라서 이를 해결하기 위해서는 아래 두 가지 해결책을 떠올릴 수 있다.

  1. ruby 버전을 2.6.0 이상으로 올린다.
  2. multipart-post의 버전을 2.2.0으로 내린다.(에러 메시지에서 권장한 방법)

두 방법 모두 Travis CI가 동작하는 환경에 대한 조작이 필요하므로, .travis.yml 파일을 건드려야 한다. 쉽게 끝날 수도 있겠지만 아닐 가능성이 더 커서 꺼려지는데, 어느 쪽이 더 쉽고 문제가 적을지를 생각했다.

1안: ruby 버전 업그레이드

2안 같은 버전 다운그레이드는 결국 문제를 나중에 터지도록 미루는 것이라는 생각이 들었다. 또 몇 시간 전까지도 잘 동작하다가 에러가 발생한 상황이어서, ruby 2.6.0은 비교적 최근에 릴리즈된 버전일 것이라 예상했고, 새로운 버전을 쓰면 좋을 것이라는 생각이 들었다. 하지만 확인 결과, ruby 2.6.0의 릴리즈 일자는 2018년 말이고 2.6.10도 이미 지원 종료(EOL)되었다. 이를 알게 되니 '아직도 몇년 전 버전의 루비를 사용하는 것을 보니 역시 고수들도 dependency는 함부로 건드리기 어렵구나' 라는 생각에 갑자기 버전 업그레이드가 부담스러워졌다. 애초에 에러 메시지에서도 multiport-post의 버전을 낮출 것을 권장하였으니, 2안에 대해 알아보는 것이 좋을 것 같다.

2안: multipart-post 버전 다운그레이드

몇 시간 전만 해도 되던 걸 안 되게 만든 주범으로 추정되는 multipart-post는 직접적으로 확인할 수는 없었지만 에러 메시지를 통해 추정하면, 몇 시간 새 2.2.0 이후 버전이 적용되면서 Travis CI의 가상환경에서 사용되던 ruby 버전과 충돌을 일으킨 것으로 추정된다. 확인을 위해 rubygems.org에서 multipart-post를 찾아본 결과, 2022년 6월 9일에 릴리즈된 2.2.2 버전은 ruby 2.6.0 이상을 요구하였다. 하지만, 그 다음날에 릴리즈된 2.2.3 버전은 ruby 2.3.0 이상으로 요구 조건이 완화되었다. 즉, multipart-post 2.2.3 버전을 사용하면 현재 버전인 2.4.335도 문제없이 사용이 가능한 것이다.

고마워요, ioquatix!

따라서, multipart-post를 관리하는 고수님들의 빠른 조치(https://github.com/socketry/multipart-post/pull/95) 덕에, 나는 아무 것도 하지 않아도 저절로 문제가 해결되는 기쁨을 누렸다. 실제로 아무 조치 없이 빌드를 재실행한 결과, 빌드에 성공하였다. 내가 Travis CI가 사용하는 ruby의 버전을 지정하는 방법을 배워야 하는 사태가 발생하기 전에 Travis CI팀이 default ruby 버전을 계속 업그레이드 해주길 바라면서, 또 이 포스트의 속편을 바로 작성해야 하는 일이 없기를 바라면서 성공한 빌드 로그로 포스트를 마친다.

Installing deploy dependencies
Logged in as @bastionsofwill (bastionsofwill)

Preparing deploy

Deploying application
cd /tmp/d20220612-6975-s1zerh/work
commit fc19656296bd70de2ed24bdb2bf1c36bea7e3b67
Author: Deployment Bot (from Travis CI) <deploy@travis-ci.org>
Date: Sun Jun 12 12:07:27 2022 +0000
Deploy bastionsofwill/bastionsofwill.github.io to github.com/bastionsofwill/bastionsofwill.github.io.git:gh-pages
2021/07/14/web-domain-architecting/index.html | 251 ------------------------
2021/07/22/email-tracking/index.html | 13 +-
2021/08/01/tslop-04/index.html | 8 +-
2021/08/01/tslop-05/index.html | 8 +-
2021/08/06/what-is-ssl-certificate/index.html | 8 +-
2021/10/05/video-through-web-0/index.html | 8 +-
2021/11/17/hexo-travis-ghpage/index.html | 8 +-
2021/11/19/what-is-dependabot/index.html | 8 +-
2021/12/13/log4j2-vulnerability/index.html | 8 +-
2022/01/12/encoding-and-video-codecs/index.html | 8 +-
...
48 files changed, 129 insertions(+), 925 deletions(-)
cd -
Done. Your build exited with 0.

출처

https://hexo.io/ko/docs/github-pages https://docs.travis-ci.com/user/for-beginners https://docs.travis-ci.com/user/deployment https://docs.travis-ci.com/user/deployment/pages/ https://github.com/travis-ci/dpl https://rubygems.org/gems/dpl-pages https://rubygems.org/gems/multipart-post https://www.ruby-lang.org/ko/downloads/

· 10 min read

이게 대체 뭔 소리야

#!/bin/bash
set -eo pipefail
ID=$(dd if=/dev/random bs=8 count=1 2>/dev/null | od -An -tx1 | tr -d ' \t\n')

AWS Lambda를 공부하던 중에 샘플 코드에 위와 같은 내용이 있었다. 이를 이해하는 데 제법 오랜 시간이 걸렸으나 그 과정에서 알게 된 것이 많다.

하향식으로 접근하면, 위 코드(쉘 스크립트)는 3개 행으로 구성되어 있으며, 아래와 같은 내용으로 구성된다.

  • 1행: shebang
  • 2행: set 명령어와 그 옵션
  • 3행: $, |, dd, dev/random, 2>/dev/null, od, tr

특히 3행은 외계어나 다름없어 보인다. 이를 하나씩 뜯어서 살펴보자.

Shebang

#!의 조합으로 시작되기 때문에 이러한 이름이 붙었다. 따라서 '셔뱅'이라고 읽으며, sha-bang, hash-bang이라고도 한다. 프로그램으로서 실행되는 스크립트임을 알리기 위해 스크립트 시작점에 작성하는 문자열이며, 나머지 부분은 인터프리터 지시자로 해석된다.

#! /bin/bash

특정 경로에 존재하는 어떤 스크립트의 첫 행이 위와 같다면, Unix-like OS는 그 스크립트를 프로그램으로 인식하며, 지정한 인터프리터, 즉 /bin/bash를 실행하면서 해당 스크립트가 위치한 경로가 그 인수로 전달된다.

set -eo pipefail

set은 로컬 환경변수의 조회 및 쉘 환경 설정 기능을 수행한다. 환경변수를 조회한다는 점에서 env명령어와 유사하지만, env명령어는 글로벌 환경변수의 조회만 가능하다는 점에서 그 차이가 있다.

  • -e옵션: 오류 발생 시 프로세스 중단
  • -o pipefail옵션: pipe 오류 코드 승계 즉, set -eo pipefail 명령을 실행하면, 이후 실행되는 pipe로 연결된 스크립트 중 하나만 non-zero exit code를 반환하여도 프로세스가 중단된다.

|

파이프 명령어로, 파이프 왼쪽에 위치한 (출력)값을 오른쪽에 위치한 명령어의 입력으로 전달한다.

ls -al | grep "yml"

예를 들어, 위 스크립트는 | 왼쪽의 ls -al의 출력을 오른쪽 grep "yml"의 입력으로 전달한다. 따라서, ls-al의 출력값은 cli로 출력되지 않고 grep "yml"의 입력로 전달되며, 결과적으로 현재 디렉토리에 존재하는 파일과 디렉토리의 리스트(ls -al의 출력) 중에서 yml이라는 문자열을 포함하는 행만 출력된다.

$와 명령어 치환(Command Substitution)

쉘 스크립트에서 $는 변수를 가리키기 위해 사용된다. 예를 들어, $VAR또는 ${VAR}과 같이 VAR라는 변수의 값에 접근할 수 있다.

$(cat file.txt)

하지만 위의 예시에서 $는 변수의 값에 접근하기 위해 사용된 것이 아니라, 명령어 치환을 위해 사용되었다. $(command)형태의 스크립트는 자기 자신을 subshell 환경에서 command 명령어를 실행한 표준 출력으로 대체한다. (다만 예시는 $(< file)와 동치이며, <를 사용하는 것이 더 빠르다.)

dd

dd 명령어는 stdin을 stdout으로 복사하는 역할을 한다.

  • bs=n: input, output 블록 사이즈를 n byte로 설정한다.(디폴트: 512바이트)
  • count=n: n개의 블록만 처리한다.
  • if=file stdin 대신 file로부터 input을 받는다. 즉, /dev/random이라는 파일로부터 8바이트의 블록 1개를 읽어와서 출력하는 것으로 해석할 수 있다. 그런데 /dev/random은 뭘까...?

/dev/random, /dev/null

리눅스 파일 시스템에서는 모든 대상을 파일 또는 디렉토리로 추상화하여 관리한다. 디바이스에 입력/출력을 실행하는 것은 해당 디바이스를 추상화한 파일에 쓰기/읽기를 수행하는 것과 같다. /dev 디렉토리는 스페셜 파일 또는 디바이스 파일이 위치한 디렉토리이다. 대부분의 디바이스의 종류는 아래와 같이 크게 2가지로 나뉜다.

  • 블록 디바이스
    • 블록/섹터 등의 단위로 데이터를 전송하는 디바이스
    • 주로 데이터를 저장하는 역할을 수행
    • 리눅스 파일 모드에서 b로 표시
    • 하드디스크, CD/DVD 등
  • 캐릭터 디바이스
    • byte 단위로 데이터를 전송하는 디바이스
    • 리눅스 파일 모드에서 c로 표시
    • 키보드, 마우스 등

하지만 물리적 디바이스와 연결되지 않는 디바이스 파일도 있는데, 이를 pseudo-device라고 한다. 커널 레벨에서 동작하는 기능들을 제공하는데, /dev 디렉토리에 있는 대표적인 pseudo-device들은 아래와 같다.

  • /dev/null: 모든 입력을 버리는 블랙홀과 같다. 출력(읽기)을 하면 EOF를 반환한다.
  • /dev/zero: 마찬가지로 모든 입력을 무시하며, 출력 시 null 문자의 stream을 반환한다.
  • /dev/full: 입력 시도시 ENOSPC(disk full) 에러를 반환하며, 출력 시 null 문자의 stream을 반환한다.
  • /dev/random: 입력한 데이터는 엔트로피 풀에 추가되며, 출력 시 커널의 암호학적 유사난수 생성기(CSPRNG)에서 생성된 데이터를 출력한다.

2>

컴퓨터 프로그램은 실행 환경과 통신하기 위한 stdin, stdout, stderr라는 3개의 데이터 스트림을 가지며, 각각 0, 1, 2라는 정수 file descriptor 값을 갖는다. 즉, 2>는 표준 에러의 출력을 지정하는 스크립트이다.

od

Octal Dump의 약어로, 특정 입력을 사용자가 지정한 포맷으로 출력한다.

  • -A: 입력의 주소값을 출력하는 방식을 지정한다. d, o, x, n 네 옵션이 있으며, 각각 decimal, octal, hexadecimal, no address이다.
  • -t: 출력 포맷을 지정한다.
    • a: ascii
    • c: default char set
    • d/o/u/x: signed decimal/octal/unsigned decimal/hexadecimal. 아래 size 지정자와 같이 쓰일 수 있다.
      • C: char, 1 byte
      • S: short, 2 byte
      • I: Int, 4 byte
      • L: Long, 8 byte
      • 1/2/4/8: 10진수로 나타낸 바이트 수.

tr

translate. 입력값을 가공하여 출력한다.

  • -C/-c: 입력값 중 특정 charater set을 제외한 나머지를 지정한 값으로 바꾼 값을 출력한다.
  • -d: 입력값 중 특정 charater set을 지우고 출력한다.

그래서 이게 뭔 소리라고?

#!/bin/bash

이 스크립트는 bash로 실행되는 프로그램이다. /bin/bash를 실행한다.

set -eo pipefail

pipe 중 하나라도 fail(exit code가 0이 아님)이면 프로세스를 중단하라.

ID=$(dd if=/dev/random bs=8 count=1 2>/dev/null | od -An -tx1 | tr -d ' \t\n')

ID라는 변수에 세개의 쉘 스크립트를 아래와 같이 조합하여 할당한다. 1) 8 byte 크기의 랜덤한 값을 2) 1 byte 단위로 16진수 형태로 변환한 뒤 3) 공백, 탭, 개행문자를 제거한 값

출처

https://github.com/awsdocs/aws-lambda-developer-guide/tree/main/sample-apps/blank-nodejs/1-create-bucket.sh https://ko.wikipedia.org/wiki/%EC%85%94%EB%B1%85 https://zetawiki.com/wiki/%EB%A6%AC%EB%88%85%EC%8A%A4_set https://zetawiki.com/wiki/%EB%A6%AC%EB%88%85%EC%8A%A4_set_-e https://zetawiki.com/wiki/%EB%A6%AC%EB%88%85%EC%8A%A4_set_-euxo_pipefail https://zetawiki.com/wiki/%EB%A6%AC%EB%88%85%EC%8A%A4_env,\_set_%EC%B0%A8%EC%9D%B4%EC%A0%90 https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html https://tldp.org/LDP/Linux-Filesystem-Hierarchy/html/dev.html https://en.wikipedia.org/wiki/Device_file https://ko.wikipedia.org/wiki//dev/random https://en.wikipedia.org/wiki/Standard_streams

· 5 min read

Terraform은 오픈소스 IaaC툴이며, 이를 위해 자체 Configuration 언어인 Terraform Language를 사용한다.

Terraform Language Elements

resource "aws_vpc" "main" {
cidr_block = var.base_cidr_block
}

<BLOCK TYPE> "<BLOCK LABEL>" "<BLOCK LABEL>" {
# Block body
<IDENTIFIER> = <EXPRESSION> # Argument
}
  • Blocks: 컨테이너의 역할을 수행하며, 리소스 등 특정 개체의 설정을 나타낸다.
    • block type: 모든 블록은 타입을 갖는다.
    • labels: 타입에 따라 라벨의 갯수가 정해진다.
    • body: 다른 Argument와 블록이 중첩될 수 있다.
  • Arguments: name에 value를 할당하며, block 안에 존재한다.
  • Expressions: 참조/조합되거나 그 자체로 어떤 값이 된다.

Modules

  • Module이란 여러 개의 구성 파일(.tf 또는 .tf.json)이 한 디렉토리에 모인 것을 말한다.
  • 모듈은 같은(top) 레벨의 파일들만으로 구성되며, 하위 디렉토리는 별개의 모듈로 취급되어 구성에 자동으로 포함되지 않는다.
  • Terraform은 항상 하나의 root module 컨텍스트에서 실행되며, Terraform 구성(configuration)은 root 모듈과 그 child 모듈(root모듈이 호출한 모듈과 그 child)들의 트리 형태이다.
  • module 블록을 통해 child 모듈을 호출할 수 있으며, 아래와 같은 argument를 갖는다.
    • source: child module의 configuration file path 또는 다운로드 주소
    • version: child module 버전
    • input variables
    • meta-arguments: child 모듈을 호출하는 방식을 지정해줄 수 있다.

Resources

  • Terraform Language에서 가장 중요한 요소
  • 각 Resource 블록은 타입과 로컬 네임, 2개의 라벨을 가지며, 타입과 리소스의 조합은 각 resource의 id로 작용하므로 같은 모듈에서 unique해야 한다.
  • 로컬 네임은 같은 모듈 스코프에서 해당 리소스를 참조하는데 사용된다.

Meta-arguments

  • depends_on: 의존성 명시
  • count: 특정 갯수의 인스턴스를 생성(for_each와 동시 사용 불가)
  • for_each: map 또는 string set으로 다수의 인스턴스를 생성(count와 동시 사용 불가)
  • provider: non-default provider configuration 지정
  • lifecycle: 리소스의 생성/소멸 관련 조건 지정
  • provisioner: resource 생성 후 별도 행동

Resource Behavior

  • Resource 블록을 통해 새로운 객체가 생성될 경우, 해당 객체의 id가 Terraform state에 저장되어 관리된다.
  • 이미 state에 존재하는 Resource 블록이 있을 경우, configuration과 객체를 비교하여 필요시 객체를 configuration에 맞게 update한다.

Data Sources

  • data블록은 'data source'와 'local name' 2개의 라벨을 가지며, 특정 data source로부터 데이터를 읽어 와서 local name 아래에 결과를 export한다.
  • variable 블록과 마찬가지로 참조에 의한 값을 할당한다는 공통점이 있지만, variable 블록은 Terraform configuration 안에서 정의되는 반면 data 블록은 클라우드 인프라, 애플리케이션 등 외부에서 발생하는 데이터를 참조한다는 차이점이 있다.

Providers

  • Providers Terraform에서 클라우드/SaaS/기타 API 제공자와 상호작용하기 위해 사용하는 플러그인이다.
  • Provider는 resource 및 data source의 집합을 추가해 준다.

Variables and Outputs

  • variable 블록은 input variable을 정의하는 데 쓰인다.
    • default: 디폴트 값
    • type: value의 타입(string, number, bool + collections 등의 복합 타입)
    • description
    • validation: condition argument를 통해 변수가 가질 수 있는 값의 조건을 지정할 수 있으며, error_message로 invalid할 경우 에러 메시지를 지정할 수 있다.
    • sensitive: boolean. Terraform UI output에서 마스킹 여부를 결정
    • nullable
  • output 블록은 모듈 외부로 값을 export할 때 사용된다.
    • value
    • description
    • sensitive
    • depends_on: 의존 관계를 명시적(explicit)으로 표현할 때 쓰인다.

출처

https://www.terraform.io/language