테라폼이 뭐지


Terraform. 이름은 몹시 멋지다. 골디락스 존에 있는 행성이나 가까이 있는 화성처럼 지구화 가능할 것 같은 곳을 지구처럼 만드는 걸 테라포밍이라고 하는데 그걸 따온 것은 아닐까 하는 생각을 했다. 어쨌든 회사에 들어오니 테라폼을 사용하고 있었을 뿐.


코드로서의 인프라스트럭처를 지향한다

테라폼은 하시코프라는 곳에서 만든 인프라스트럭처 관리 도구이다. 오픈소스다. 테라폼이 뭐하는걸까 물어볼 사람이 없어서 사이트에 들어가보니 테라폼은 인프라를 만들고 바꾸고 버전관리를 안전하고 효율적으로 하기 위한 툴 이라고 쓰여 있었다.

그러니까 인프라 구축하는데 도와주는거구나. 근데 이게 왜 필요할까?

코드로서의 인프라스트럭처를 지향한다. 웹 콘솔이나 GUI를 사용해 관리하지 않고 필요한 리소스들을 코드로 작성해 관리한다.

이제 서버를 내가 만들고 관리할 수 있는 것이 가능하게 되었다. AWS나 AZURE 등 클라우드 서비스들이 많아졌다. devops들이 생겨가면서 코드로서 인프라스트럭처를 관리하는 IAC(Infrastructure As Code)도 덩달아 주목받기 시작했다. GUI를 통해 수동으로 서버를 운영하고 관리할 수 있긴 하다. 하지만 애플리케이션처럼 코드로 인프라를 생성, 관리, 변경, 운영이 가능하다면 그게 더 편하고 안전하지 않을까?


맞아 안전한거 같아

처음에 terraform이 뭔지 잘 모르고 회사 서버를 만지작거리다가 한번 날린적이 있었는데(진짜 등에 땀 줄줄 났음) 다행히 terraform으로 서버 세팅이 잘 이루어져있어서 금방 복구 할 수 있었다. terraform이 없었으면 이전 작업자가 서버를 어떻게 세팅해놨는지도 모르고(문서화가 되어있지 않음) 이미 서버를 지구를 떠났으니 나는 등땀을 엄청 흘리다가 이실직고하고 지옥에서 착한일 하는 것 같은 죄책감속에서 동료의 따가운 눈총을 받으며 복구할 때 까지 수 없는 밤을 지새워야했을지 모른다.


테라폼은 어떻게 동작하지?

테라폼은 서버 프로비저닝을 지원한다. 프로비저닝이란 사용자의 요구에 맞게 시스템 자원을 할당, 배치, 배포해 두었다가 필요시 시스템을 즉시 사용할 수 있는 상태로 미리 준비해 두는 것을 말한다. 서버 자원 프로비저닝, OS 프로비저닝, 소프트웨어 프로비저닝, 스토리지 프로비저닝, 계정 프로비저닝 등이 있다.

우리 아빠는 기회가 왔을 때 잡을 수 있도록 미리미리 준비해두라고 했지. 테라폼은 우리 아빠가 하는 말과 잘 부합하는 툴이다. 미리 프로비저닝해두면 언제든 내가 원할 때 짠 하고 네트워크를 세팅하거나 서버를 만들 수 있다. 이거 참 신기함.

예를 들면 나는 요리를 할 때 도구는 칼 2자루, 도마 2개, 아래로 깊은 팬 하나, 넓고 평평한 팬 하나가 필요하고, 향신료는 소금, 후추는 백후추와 흑후추, 오레가노, 타임, 바질은 생바질과 바질페스토, 요리에 쓸 달지않고 싼 화이트와인 하나, 내가 마실 산미구엘 한캔이 항상 필요하다고 가정하자. 매번 요리할 때마다 저걸 챙겨야하고, 내가 아니라 다른 사람이 나를 위해 준비해주는 경우엔 하나 둘 빠뜨릴 수 도 있다. 근데 알프레도라는 집사를 고용해서 그 사람한테 한번 저 세팅을 알려줬더니 언제든 누군가 필요하다고 말만하면 짠 하고 세팅을 해주는거야. 야 진짜 완전 짱이지 않냐 이거? 대박이다 정말!

테라폼과 함께라면 AWS에서 ec2 인스턴스를 만들고 security groups와 elb 설정을 하는 일 등을 코드로 관리할 수 있다.

아래 테라폼 코드를 실행하면 서울리전에 cores기반으로 ec2 인스턴스를 t2.micro로 2개 생성할 수 있다. 선언식으로 되어 있어 전체적인 그림을 살펴보기에 편리하다.


provider "aws" { region = "ap-northeast-2" } data "aws_ami" "coreos" { most_recent = true filter { name = "name" values = ["*CoreOS*"] } filter { name = "virtualization-type" values = ["hvm"] } } resource "aws_instance" "coreos" { count = 2 ami = "${data.aws_ami.coreos.id}" instance_type = "t2.micro" vpc_security_group_ids = ["${aws_security_group.ec2_front_end.id}"] tags { Name = "example" } }


왜 코드로 관리해?

인간은 언제나 실수를 하지. 나도 항상 실수투성이 만신창이 인생이라 난 나를 믿지 않… 사람은 실수를 할 수 있다. 직접 수동으로 작업하다보면 분명 어딘가에서 실수가 발생할 수 있다. 하지만 코드로 관리되고 실행되면 이런 사람이 하는 실수를 방지할 수 있다.

위에서 말했지만 서버 운영이 자동화가 가능해지기 때문에 aws 콘솔들어가서 이거 누르고 저거 쓰고 할 필요 없이 그냥 terraform 코드만 실행시키면 된다.

코드이기 때문에 git을 이용해 쉽게 관리도 가능하다

그리고 새로들어온 개발자도 terraform 코드를 보면 인프라의 전체적인 구성을 이해할 수 있다. 그렇다. 테라폼없이 수동으로 작업한다면 인프라를 구현한 작업자가 안알랴줌하면 아무도 모른다.


마냥 좋은가

그렇지는 않은 것 같다. 처음 수동으로 구축하는 것 보다 테라폼을 통해 만들려면 쉽고 간단한 작업도 시간이 오래 걸린다. 그냥 aws 콘솔가서 ec2 인스턴스 생성 누르면 되는데 이건 뭐 설정하는게 많냐라는 생각이 들 수 있다. 하지만 한번 테라폼으로 코드화가 되면 유지보수가 쉽고 관리도 간편해지기 때문에 테라폼을 사용하는 것을 권장한다.


그러면 어떻게 사용하는가

테라폼 문서를 보며 차근차근 배워보자. https://www.terraform.io/docs/index.html

화이팅.

자바스크립트와 DOM 변경

자바스크립트에는 DOM을 다룰 수 있는 기능이 있다. 그런데 자바스크립트가 DOM을 변경할 때는 컴퓨터 자원 소모가 심하다. 둘은 기본적으로 서로 다른 언어이고(돔은 기본적으로 html로 표현 됨), 돔은 트리구조이기 때문에 검색하는데 자원 소모가 된다. 또, 자바스크립트를 통해 DOM이 변하면 브라우저 화면에서 웹페이지를 다시 출력해야 하는 상황이 발생한다.

자바스크립트로 인한 DOM의 변화는 크게 두 가지로 구분 할 수 있다. 

  • DOM repaint
    돔 엘리먼트의 위치는 변경되지 않고 배경색, 글자색, 또는 visibility가 변화할 때 일어난다.
  • DOM reflow
    DOM에 엘레먼트가 추가되거나 삭제 될 때, 이미지 크기가 변할 때 처럼 표시되는 레이아웃을 재정렬해야 할 때 일어난다. 위치를 전체적으로 다시 계산해야 하기 때문에 repaint보다 자원 소모가 크다.
    (+ 브라우저의 크기가 바뀌어도 DOM 요소들의 크기에 따라서 재정렬이 필요하여 reflow가 발생한다.)

일반적으로는 스레드 큐에 하나의 자바스크립트 실행이 끝나면 그에 대한 DOM reflow나 repaint를 실행한다.
더 나은 성능을 위해 reflow나 repaint를 최소화하도록 개발해야 한다.

DOM reflow시 자원 소모 최소화 방법

  • 변경될 DOM을 웹페이지의 아래쪽에 배치하는 것
    : 앞에 있는 엘레멘트의 위치나 크기가 변하면 그 뒤에 있는 엘레멘트들은 전부 재정렬을 해야한다. 그러므로 변경될 DOM을 가능한 한 웹페이지의 아래쪽에 배치하면 재정렬에 드는 자원 소모를 줄일 수 있다. 

  • position을 absolute나 fixed로 주기
    : position을 absolute나 fixed로 주면 그 엘레멘트는 화면에서 띄워진다. 변경될 엘레멘트를 띄워버리면 돔 전체에 Reflow가 일어나는 걸 막을 수 있다. (그래도 자식요소들에 reflow는 일어나니까 변경될 엘레멘트의 자식 엘레멘트를 최소화 할 것)

  • css의 최소화
    reflow가 일어날 땐 css의 적용도 다시 일어나기 때문에 css를 최소화 하면 자원 소모가 덜하다. 

  • 엘레먼트 추가/삭제시 Reflow 최소화 방법
    - 삽입할 때 DocumentFragment객체를 사용해서 한 번에 넣기
    DocumentFragment객체는 돔 요소들을 담을 수 있는 컨테이너 역할을 해주는 객체이다(아마 react를 사용하는 사람들은 <Fragment>요소를 본 적이 있을 것이다).
    엘레멘트를 하나하나 추가하면 추가한 횟수 만큼 reflow가 여러번 일어나게 된다. 추가 할 엘레멘트들을 Fragment라는 컨테이너에 담아 한 번에 append하면 reflow가 한 번만 일어나도록 할 수 있다. 
    부모 엘레먼트를 컨테이너로 쓸 수 있는 경우엔 부모 엘레먼트에 추가할 엘레멘트들을 전부 추가하고 부모 엘레멘트를 돔에 append하면 됨

    - display: none; 을 활용하기
    : display: none 을 통해 작업할 엘레멘트를 백그라운드로 뺀 다음에 작업을 마치고 display: block으로 바꾸기
  •   // div가 컨테이너 엘레먼트라고 가정 
      div.style.display = "none"
      ....
      안에 있는 돔 조작 
      ....
      div.style.display = "block"
    

    DOM 탐색 횟수 최소화를 통한 성능 개선

    • css 선택자에 대한 계산이 짧아지도록 id나 class를 쓰기

      최악은 div span / table tr td 이런식으로 단계적으로 태그명을 적는 것. 검색 양이 늘어난다. 

    • getElementById, querySelector와 같은 돔 탐색 함수 호출 횟수 최소화 하기

      매번 함수를 통해 element를 찾기보다 한 번 찾아서 변수에 할당해 놓기

        // 안 좋은 예
        document.getElementById("changingDiv").style.backgroundColor = "#FFFFFF"
        document.getElementById("changingDiv").style.fontSize = "10rem"
        document.getElementById("changingDiv").style.width = "100px"
        document.getElementById("changingDiv").style.marginLeft = "10px"
      
       // 좋은 예
        var changingDiv = document.getElementById("changingDiv")
        changingDiv.style.backgroundColor = "#FFFFFF"
        changingDiv.style.fontSize = "10rem"
        changingDiv.style.width = "100px"
        changingDiv.style.marginLeft = "10px"

    회사에서 특정 페이지를 유지보수해야하는 일이 생겼다.

    한 페이지에서 데이터를 적게는 2~3000건, 많게는 30~40만건씩 긁어오는 기능이 있었는데.

    이 페이지에서 과도한 양의 데이터를 요청하면, 데이터를 긁어오는 속도도 늦을 뿐더러.
    이 API를 담당하는 서버가 부하가 되서, 이 서버와 연관된 다른 서비스까지 먹통이 된다는 이슈였다.


    1, 인터넷에 찾아보니 특정 기간을 통해 긁어오는 방식은, 몽고DB의 빠른 속도를 가능케하는 인덱스를 잘 활용하지 못하는 것이라는 이야기가 있었고. 쿼리를 조금 더 구체적으로 수정했다.

    2, 몽구스용 docs를 쓰면서, 기존 몽고 DB를 편하게 그러나 무겁게 쓰는 방식이 아니라 plain object를 반환하는 lean 함수(몽구스 내장)를 사용하면 더 빠르게 데이터를 가져올 수 있다는 것을 들었고 실행해봤다.


    속도는 개선되었으나 큰 데이터를 받아왔을때에는, 이 일을 처리하는 노드서버가 터져버린다는 단점이 있었다.

    인터넷에서 몽고 DB에서 빅데이터를 긁어오는 방식을 찾다가 mapreduce와 aggregation를 보게 되었다. 몽고에서 쿼리를 통해 데이터를 가져옴과 동시에 가공처리를 할 수 있게 도와주는 것들인데

    이것들을 활용하면, 노드가 받는 부하도 해결될 수 있을것 같았다.


    둘 중에 어떤 방식을 쓸까 고민했는데 많은 사이트에서 퍼포먼스 이유로 aggregation을 쓰는 것을 추천했다.
    ( https://medium.com/@MicroPyramid/group-vs-aggregation-framework-vs-mapreduce-in-mongodb-a40b252e798b )


    (https://www.mongodb.com/presentations/aggregation-framework-0 aggregation 영상) 출처: 몽고 DB

    aggregation은 pipeline을 통해 몇번의 스테이징과정을 거치면서 데이터를 불러오고 가공해서 원하는 결과를 반환해주는 프레임워크라고 한다. (데이터를 긁어 올 때, 정말 다양한 처리를 할 수 있어서 만능키라고 영상에선 소개한다.)

    https://docs.mongodb.com/manual/reference/operator/aggregation/match/ aggregation안의 다양한 오퍼레이터들이 있어서 이를 활용하면 데이터를 매우 효과적으로 가져올 수 있다.


    aggregate안에 여러 오퍼레이터를 array에 넣어 pipeline을 만든다. 저 czxc에 원하는 데이터가 나온다!

    간단하게 설명하자면 

     $match는 쿼리에 맞는 조건을 긁어오는 것이고.
     $project는 원하는 필드만을 남기고 그 외의 필드들은 제거하는 필터과정이고.
     $sort는 정렬
     $group은 원하는 조건으로 데이터를 묶는 것이다. ( 나의 경우는 dw, ad라는 특성으로 묶은 후, sort 과정까지 겪은 docs를 
      버리지 않고, orders에 집어넣는 과정을 거쳤다)


    이 방법으로 꽤나 큰 데이터들도 빠르게 & 노드가 터지지 않으면서 긁어올 수 있었다.


    그런데... 여기서 문제가 또 생겼다...

    데이터를 grouping하는 과정 중에 orders에 기존 docs들을 채워 넣으면서 하나의 docs 크기가 64M가 넘어가는 케이스가 생겼다. 이 경우, 몽고 내부적으로 에러가 난 것이다 ㅠㅜ. 이야기 한 결과 충분히 실제 서비스에서도 일어날 수 있는 문제였다.

    (몽고 docs size limit - BSON 16mb )


    결과적으로는.. 몽고 find 메서드를 통해 넘겨주는 결과인 Cursor 자체를 받자마자 클라이언트에 넘기는 방식으로 이를 해결했다.
    클라이언트는 데이터를 스트리밍 방식으로 꾸준히 받은 다음 다 받고 나서 자체적으로 데이터를 가공해 뷰에 띄워주는 식이었다.



    이 형식으로, 약 200MB의 데이터를 4분간의 시간을 통해 받아 가공 처리 해서 보여줬다. 다행이도 이 과정 중에 몽고나 노드가 터지는 일은 일어나지 않았다!


    그런데 아마, 곧 추가 작업을 해야할 것 같다 ㅎㅠㅠ 흑...

    '개발개발' 카테고리의 다른 글

    Test-Driven Development(TDD)란 무엇인가?  (2) 2018.12.30

    우리 회사 개발 업무 특성 상 일률적으로 들어오는 상품 데이터 각 오픈 마켓에 맞게 가공 해야 할 때가 많다. 상품명에 특정 특수문자가 들어가지 않아야하고 length가 70자가 넘지 않아야 하는 아주 간단한 가공부터 상품 옵션이 각 마켓에서 필요로 하는 조건으로 바꿔줘야하는 아주 까다로운 것 까지 다양하다.


    그 중 내가 개발한 알고리즘(?) 중에 가장 뿌듯했던 것을 소개해보고자 한다.


    그것은 바로 키워드 추출 알고리즘이다.


    여기서 말하는 키워드란 오픈 마켓을 사용하는 이용자가 어떤 상품을 찾기 위해 가장 많이 사용하는 검색어를 말한다. 모든 마켓에서 키워드가 필요하지만 쿠팡에서는 키워드와 가격 설정이 상품을 상위 노출 시키는데 다른 마켓보다 더욱 중요하다 (쿠팡에서도 키워드 입력을 적극 추천한다). 가격 설정 알고리즘도 개발하였는데 이 부분은 나중에 소개할 기회가 된다면 블로그에 올리도록 하겠다.


    우리 회사가 쿠팡 이용자들이 사용하는 검색어들을 실시간으로 추적하여 데이터를 가지고 올 수 있다면 더욱 정확한 분석을 통해 키워드를 추출할 수 있겠지만 그게 아니니 쿠팡 검색어 자동 완성 단어들을 가지고 오기로 했다. 하지만 어떠한 이유로 쿠팡에서 Open API에 공개된 API외에는 해외 IP들을 차단되게끔 해놔서 기껏 다 만들어 놓고 사용할 수 없게 되어 네이버 쇼핑 검색 api를 이용하게 되었다.

    먼저 한 단어를 검색했을 때, 자동완성 되는 단어들을 결과값으로 얻게 되는 함수이다. 


    하얀색 네모로 표시된 부분은 네이버 쇼핑 api를 통해 자동완성 되는 부분을 리턴값으로 가져온다. 하지만 리턴값이 JSON이 아닌 HTML이므로 파란색 네모 부분처럼 처리를 해줘야 HTML리턴값을 확인 할 수 있다. 하지만...

    보시는 바와 같이 엄청 추접하게 나온다. 다른 검색어들로 여러번 돌려본 결과, 녹색 네모부분은 공통적으로 나온다는것을 파악했고 그 부분의 인덱스를 파이썬 문자열 내장 함수인 rfind()로 찾아내서 잘라내고 노란색 부분을 통하여 nested list들까지 flatten 시켜보았지만 .. 

    여전히 추접하다. 이때쯤 그만두고 싶었지만 조금 더 자세히 들여다 보니 각 빨간색 동그라미 부분과 같이 4i 인덱스에서 내가 필요로 하는 키워드들이 있다라는 것을 파악했다.


    그래서 빨간색 네모 부분과 같이 0번 인덱스부터 4번째 인덱스들을 추출하도록 하는 for loop을 만들었는데 이 부분에서 문제가 발생했다. 정상적인 검색어들의 범위를 벗어나게 되면 검색어와 상관없는 숫자 또는 url들이 나와서 아래와 같이 쓸데없는 것들이 키워드 리스트로 들어가게 되는 것이다. 


    고민을 많이 했다. 어떻게 하면 효과적으로 쓸데없는 것들을 처리할 수 있을까.. 고심끝에 얻은 결론은 무의식적으로 사용하는 try, except구문을 사용하는 것이었다. 통상적으로 정상적인 부분을 처리하는 부분이 try이고, try에서 발생하는 에러들을 처리하는 부분이 except인데 이것을 거꾸로 사용하면 어떨까하고 시도해본 방법이었는데 다행히도 성공적이었다.

    빨간색 네모의 try를 보면, 리스트 안의 item들을 돌면서 전부 int(item)을 거치게 한다. 스트링을 int화 시키게 되면 에러가 생기게 되고 에러가 생긴다면 숫자가 아닌 것이고 숫자가 아니라면 내가 찾는 검색어이므로 final_keyword_list로 들어가게 된다. 따라서 내가 찾는 또는 정상적인 부분을 에러로 인식하게끔 만들어 except부분에서 처리하는 것이다.

    위에 올린 코드에는 나와 있지 않지만 이 블로그를 작성하다가 url도 포함될 수 있다라는 것을 발견했고 except부분에 아래와 같이 추가하였다. Url이라면 http는 필연적으로 가지고 있을테고 url을 포함하고 있다면 그 부분은 건너뛰게 했다.


    그래서 결과적으로...

    정상적인 키워드 리스트들을 리턴하게 되었다. 이 함수의 기능은 '한 단어'를 통해 자동 완성 되는 검색어들을 리턴하는 것이고, 이 함수를 for loop으로 돌려서 최종 키워드들을 리턴하는 함수는 따로 있는데 이 부분은 간단하므로 패스하겠다.


    결론:

    알고리즘이라고 다 대단히 어렵고 복잡한 것은 아니라는 것을 이번 기회를 통해 알게 되었다. 매일 아침 toy문제들을 포기해보면서 '아 알고리즘은 나랑 안맞는것 같다', '너무 어렵다' 라고 생각했지만 주구장창 그것만 연습해서 잘 푸는것 보다 간단한 알고리즘이라도 목적에 맞게 사용한다면 그게 바로  실전 알고리즘이고 참된 개발이 아닐까 생각해본다.


    개발 이야기지만 다분히 비개발 적인 어조로 풀어내는 이야기. 


    CI CD? 

     CI (Continuous Integration), CD (Continuous Deploy)의 준말이다. 

    지속적인 통합과 지속적인 배포 ..? 감은 잘 오지 않는 단어이다. 그래서 나의 언어로 조금 바꿔보면 이런식으로 표현 할 수 있을 것 같다.  

     git에 지속적으로 merge 되고, 그 코드가 지속적으로 프로덕트에 반영 되는 것. 

      아직도 조금 생소 할 수 있을 것 같다. 그렇다면 일반적으로 우리가 development 단계에서 코딩을 하고, 이 코드가 웹 상에 배포 되기 까지는 어떤 과정을 거칠까?  우선 우리는 협업 및 버전관리를 하기 위해 git을 활용 할 것이고, 이런 git을 조금더 수월하게 관리 하기 위해 github라는 플랫폼을 사용하게된다. 

     그래서 혼자 혹은 두명이상의 개발자가 하나의 코드베이스를 두고 개발을 시작하게된다.  개발을 하게 되면서, 특정 feature의 작업이완료되면 아마 github원격 저장소로 pr을 날리게 되고, 여러 pr이 모여서 하나의 코드가 되어간다.

      여러 개발자 들의 여러 커밋을 거치면서, PR들이 merge 되는 과정, conflict되는 과정 conflict를 resolve 하는 과정등을 거치면서 코드는 MVP가 되어 간다.  

    그럼 MVP가 된 코드는 어떻게 배포 되는가 ?  우선 `배포`에 대해서 내가 내린 정의는 다음과 같다.  


     localhost에서만 동작하고 있던 나의 코드를 나 이외의 사람들이 접근 할 수 있는 컴퓨터에서 code를 run하는 것. 


    그렇다. 코드가 배포가 될려면, 내 컴퓨터 상에서는 할 수가 없다.(물론 가능은 하다.)  

    내가 가지고 있는 컴퓨터 상에서 배포를 하는것이 매우 제한되기 때문에, 우리는 배포를 할려면 누구나 접근 할 수 있는 컴퓨터를 빌리거나 사야한다.  

    예전에는 그 컴퓨터를 사서 흔한 건물에 하나씩 있는 서버실을 구축 하고 해당 컴퓨터에 내 코드를 넣어서 배포를 했었다고 한다.

     하지만 우리는 아마존의 시대에 살고 있다.  더이상 물리적인 선을 직접 연결해서 (사실 누군가는 하고 있겠지만) 서버를 배포 하지 않아도 된다. 

    Image result for aws

    우리는 컴퓨터를 빌릴 수 있고, 거기서 배포를 할 수 있다.  자, 그럼 우리는 아마존(혹은 Microsoft, KT? 등등의 cloud service)에서 컴퓨터를 빌렸다. 

    하지만 이 컴퓨터는 UI 가 있는 것도 아니고 그냥 단순 터미널에 접속만 할 수 있을 뿐이고, 물리적 컴퓨터가 아니기 때문에 usb포트도 없다. 

     우리가 작성한 코드를 여기 컴퓨터로 전송 해야되는데 물리적 실체가 없으니 참으로 난감하다. 이떄 필요한 것이 git 원격 저장소이다.  

    aws에서 우리가 빌린 컴퓨터는 다행히도 인터넷에는 연결 되어 있다. 따라서 github 원격 저장소에 접속해서 우리 코드를 복사해갈 수 있다. 

     git에서 코드를 내려받기 까지 했다. 하지만 배포는 여기서 끝이 아니다. 


    이 컴퓨터는 내컴퓨터가 아니다. 내가 깔아 놓은 그 흔한 node도 깔려있지 않다. 근데 내가 작성한 코드는 node 라는 런타임 위에서 작동한다. 

    그렇다 우리는 node까지 깔아주어야 한다.  컴퓨터가 똑똑한 줄 알았는데 생각보다 멍청하다. 다 해줘야 한다.  

    아무튼 우리는 컴퓨터를 빌렸고, 코드도 복사했고, 복사한 코드가 뛰어 놀 수 있는 런타임도 깔아줬다. 

    이 코드를 런타임에서 뛰어놀게 해주면 1차적인 배포는 성공이다.(말은 쉽지만 과정이 참 길다.)  

    첫 MVP가 배포가 되었다. 주제가 CI / CD였는데, 한참동안 코드 배포에 대한 이야기를 했다.  

    내가 배포에 대해서 한참동안 얘기 한 이유는, 배포를 하는 과정이 이만큼 귀찮다는 것이다. 

    한번 배포 셋팅이 종료 된 이후에도 우리는 코드를 업데이트 하기 위해서,


    1. 코드를 작성한다.

    2. 깃허브에 코드를 올린다.

    3. 컨플릭이 있다면 해결하고, 이후에 코드를 머지한다.

    4. 머지한 코드를 다시 aws컴퓨터에 원격 접속해서 git을 통해서 pull 받아온다.

    5. 코드를 다시 실행 시킨다.


    6. ....? 안된다...


    열심히 위의 과정을 거쳐서 배포 했더니 안되면 ..? 우리는 코드를 다시 뒤로 롤백 해야한다.


    배포라는 것은 참으로 험난한 일이다. 이렇게 자꾸 배포가 힘들다는 것을 얘기 하는 이유는, CI / CD 라는 것이 이 모든 과정을 매우 수월하게 해주기 때문이다.

    CI / CD 라는 시스템을 구축 한다는 것은 다음과 같다.

    CI : 지속적으로 코드를 통합한다. 자동으로. 그러려면? 이 코드가 merge가 되어도 되는 코드인지에 대한 보장이 필요하다.

    CD : 지속적으로 코드를 배포한다. 자동으로. 그러려면? 이 코드가 배포가 되어도 되는 코드인지에 대한 보장이 필요하다.

    위의 과정을 보장 하려면? 정답은 test다. 내생각에 위 과정 중 1 ~ 5의 과정을 자동화 하는 것은 생각보다 간단한 task일 수 있고, 이를 위한 엄청난 좋은 툴 들이 존재한다.

    CI를 하면서 코드에 대한 보장을 받기 위해서 pull request 를 날린 코드를 특정 독립 환경에서 실행 시키고, 각종 테스트 들을 돌려본다.

    (circle ci가 빙글빙글 돌면서 v표시 혹은 x표시를 띄우는 것이 이런거다.)

    CI tool 이 돌린 테스트가 모두 정상적으로 통과가 되었다면, 우리는 코드 통합을 한다.

    이제 디플로이만 남았다. 깃허브 원격 저장소에 통합된 코드를 CD를 위해서 또 다른 독립적인 배포환경과 유사한 곳에서 실행 시켜본다. 이때 아마 docker를 활용 한다.

    이번엔 또 독립적인 Docker container에서 배포환경과 최대한 유사한 환경을 구축 하고 이번엔 github에서 통합된 코드를 가져와 마찬가지로 테스트 과정을 거친다.

    Image result for docker


     아마도 테스트를 돌려보니 실패가 있으면 아마도 코드 배포를 진행 하지 않을 것이고, 테스트가 성공적 이라면, staging을 할 것이다. 물론 이번에는 다양한 이유로, ( 마케팅팀이 싫어해요, 유저경험이 별로에요)등의 이유로 최종 프로덕션에 배포 되지 않을 확률도 존재하지만.

    만약 CI CD시스템을 구축하지 않는다면?(사실 나도 구축하지 않은 서비스를 만들고 있다..)

    우리는 PR을 날리기전에 여러가지 테스트 들을 수동으로 해봐야 한다. 블로그를 예로 들면 글을 작성 해보고 지워도 보고 로그인도 해보고 수정도 해보고 등등등.

    로컬에서 잘되서 코드를 Merge 하였으면, 실제 프로덕트 배포환경과 유사한 환경, 거의 동일한 환경에 배포를 하고 또 글을 지워보고 작성해보고 지워도보고 로그인도해보고.. 등등..

    잘되서 이번엔 프로덕션 코드에 합친다. 프로덕션 코드에 합친 뒤에도 로그인도 해보고 지워도 보고 .. 등등등

    몹시 개발자스럽지 않은 과정을 손수 거쳐야 한다.(하고 있다..)

    아무튼 내가 두서없이 써내린 글들을 요약하면, 내가 작성한 코드가 배포가 되기 위해서는 엄청 복잡한 과정을 거쳐야 하고, 이 복잡한 과정을 단순하게, 자동화 해주는 것이 CI / CD 이다.

    물론 그 과정을 자동화 하는 과정 자체는 엄청 복잡 할 수 있지만, 되어 있지 않은 체로 프로덕트가 이미 사이즈가 커졌을 때 감내해야 하는 과정을 생각 하면!

    나처럼 초주니어 개발자들 이라도, CI CD 과정을 한번쯤 공부 해보는 것도 나쁘지 않다고 생각 한다!

    회사에서 내가 만든 컴포넌트가 어떻게 배포되는지도 모르고 앉아서 코딩만 하고 있는 코더가 되지 않길 바라는 마음으로 글을 마무리 하겠다!


    '백엔드' 카테고리의 다른 글

    테라폼이 뭐지  (0) 2019.03.23


    파이썬을 쓰다보면 '@' 이런 모양을 종종 만나게 된다. 저것은 데코레이터(decorator)라고 하는 것이다. 난 데코레이터를 처음 봤을때 '오 귀여운 골뱅이네'라고 생각했다. 플라스크를 처음 사용할 때도 귀여운 골뱅이를 보았다.


    from flask import Flask
    app = Flask(__name__)

    @app.route("/")
    def hello():
    return "Hello World!"


    위 예시 코드에서 '@app.route("/")' 이 부분이 바로 데코레이터다.


    그렇다면 데코레이터는 무슨 일을 할까?

    코드를 바꾸지 않고 기존 함수를 수정할 때 사용할 수 있다. 데코레이터는 함수를 인자로 받아 다른 함수를 출력한다. 직관적으로 이해가 되지 않으니 예제를 한번 만들어보자.


    def document_it(func):
    def new_function(*args):
    print('Running function:', func.__name__)
    print('Arguments:', args)
    result = func(*args)
    print('This is result:', result)
    return result
    return new_function


    함수의 이름과 인자, 그리고 결과를 알려주는 데코레이터 함수를 만들었다! 이 데코레이터 함수는 함수를 인자로 받아 새로운 함수를 반환한다. *args는 파이썬의 위치인자이다. **kwargs를 넣으면 키워드 인자까지 알려줄 수 있지만 예제에선 위치인자만 넣어보았다.


    그럼 한번 사용해보자.


    def give_me_beer(a):
    return "I need {} more beer!!".format(a)

    give_me_beer(5)
    >> I need 5 more beer!!

    another_give_me_beer = document_it(give_me_beer)

    another_give_me_beer(900)
    >> Running function: give_me_beer
    >> Arguments: 900
    >> This is result: I need 900 more beer!!
    >> I need 900 more beer!!


    위에서는 수동으로 데코레이터함수를 할당했다. 그래서 귀여운 골뱅이 '@' 모양을 볼 수 가 없다.

    데코레이터를 사용하고 싶다면 사용하고 싶은 함수 위에 '@decorator_name'을 쓰면 된다.


    @document_it
    give_me_beer(5)
    >> I need 5 more beer!!


    give_me_beer(900)
    >> Running function: give_me_beer
    >> Arguments: 900
    >> This is result: I need 900 more beer!!
    >> I need 900 more beer!!


    짠 이제 give_me_beer만 실행해도 내가 원하는 데코레이터 함수가 적용되어 작동한다. 예제에서는 하나만 썼지만 데코레이터는 여러개를 붙여서 사용할 수도 있다.



    데코레이터를 어떤 경우에 쓸까

    함수 마다 실행되는 시간을 측정해야 되는 경우가 있다고 가정해보자. 함수가 약 20개 정도 되는데 모든 함수의 소스코드에 실행 시간을 측정하는 코드를 짜 넣는 것은 비효율적이다. 게다가 매우 귀찮은 과정이 될 것이다.

    그럴 때 시간을 실행 시간을 측정하는 데코레이터 함수를 만들어 함수의 위에다 붙여주기만 하면 각 함수의 코드를 변경하지 않아도 실행 시간을 측정할 수 있지 않을까?(물론 다른 방법들도 생각이 났는데 이건 데코레이터 포스팅이니까)


    프로젝트를 하다보면 언젠가 요긴하게 쓸 날이 올 것 같은 기분적 느낌이 드는 기분이다. 그 날이 오면 골뱅이 무침에 맥주를 마시러 만선호프에 가자.

    이 글은 TDD(Test-Driven Development)에 대한 경험이 없는 사람들을 위한 글이며, 

    테스트 케이스를 왜 짜는지, 개발 단계별로 어떤 종류의 테스트 케이스가 필요한지에 대한 이해를 돕기 위해 쓰였습니다. 

    (*참고: 자바스크립트 리액트 코드에 대한 테스트 케이스를 연구하다가 쓰게 된 글입니다. 다른 개발환경에서는 약간 다르게 적용될 수 있습니다. 제가 오해하고 있는 부분이 있거나 기타 다른 의견이 있으면 첨언 해주시면 감사하겠습니다!)


    목차

    1. TDD를 하는 이유 

    2. TDD의 과정

    3. 테스트 피라미드(테스트의 계층) 


    TDD를 하는 이유

    • 코드를 짜기 전에 놓치는 것이 없도록 코드의 기능에 대한 체크리스트를 만드는 것이다.
    • 초기 단계에서 버그를 잡을 수 있다. 개발 단계에서 버그를 잡는 것은 프로덕션 단계에서 버그를 잡는 것보다 비용을 훨씬 절감시켜 준다.
    • 예상 시나리오를 미리 돌려보면서 가정들을 실험해 볼 수 있다.
    • TDD는 코드 리팩토링을 안전하게 할 수 있게 해준다.(코드를 바꿔도 모든 로직이 정상적으로 작동하는지, 빠트린 건 없는지)
    • TDD를 하면 우리는 시스템에 대한 테스트 리포트를 받게 되는데, 이를 통해 프로덕트 오너나 비지니스 애널리스트에게 코드에 대한 시각적 자료를 제공할 수 있게 된다.

    TDD의 과정

    • 코드에 대한 테스트를 적는다.
    • 테스트가 실패하는 것을 본다.
    • 테스트를 통과할 수 있도록 코드를 짜서 테스트를 통과시킨다.
    • 테스트를 더욱 자세하게 리팩토링 한다.
    • 다시 테스트를 해서 코드가 테스트를 통과하도록 한다.
    예) 두 숫자를 넣으면 합을 반환해주는 calculator를 만든다고 가정 (*가장 흔하게 사용되는 테스트 프레임워크인 mocha를 사용했습니다.)

    1. 시스템을 설명하는 테스트 코드를 먼저 작성한다.

    2. 돌려보면 실패함.(에러 메세지를 받음)

    3. Calculator class만들고 다시 테스트를 돌려봄. 통과함.

    4. 추가적인 메쏘드를 포함한 테스트 케이스를 다시 작성함.

    5. 테스트는 실패하고 에러가 뜸.

    6. Calculator.add 메쏘드를 추가시킴. 일단 테스트를 통과할 수 있게 3을 리턴하는 함수로 만듬. 

    7. 테스트 통과.

    8. 테스트의 expectation을 바꿔 봄.

    9. 테스트는 실패함.

    10. 맞는 코드를 짜서 다시 통과시킨다.
     

    테스트 피라미드 


    TOP : UI test

    하나하나의 페이지를 테스트하기 보단 모든 흐름을 꿰뚫는 business critical feature만 테스트 해야함.

    실제 브라우저가 필요하기도 함. 느리고 비용 많이 듬.


    MIDDLE : Service level tests or Contract test 

    주로 API테스트. downstream dependencies나 외부 API검사하는 것.

     

    BOTTOM: Unit test  

    모든 것에 관한 테스트. 로직테스트가 될 수도 있고 컴포넌트 연결 테스트가 될 수도 있음. 리팩토링 할 때 이슈 잡는 테스트일 수도 있음. 

    빠르고 유지 비용이 적음. 


    "high level test를 두 번째 방어책으로 생각해야 한다. 

    윗 단계 테스트에서 실패가 뜨면 Unit Test에서 빠트린 부분이 있다는 뜻이다."

    -마틴 파울러-


    (즉, 유닛 테스트를 빠트리는 것 없이 모든 것에 대해 꼼꼼하게 작성해야 한다!)




    ※ 참고자료

    Test-Driven Development for React/Redux in an Isomorphic Application by Hany Elemary: 

    (https://www.safaribooksonline.com/videos/test-driven-development-for/9780134698410/)


    ※ 더 읽을 자료

    Unit Test Basic: https://wiki.lucashan.space/basic/01.unit-test-basic.html#_1-unit-test

    TDD 읽은 티 내기: https://brunch.co.kr/@moonjoonyoung/7





    '개발개발' 카테고리의 다른 글

    몽고 db 삽질  (0) 2019.02.17

    + Recent posts