반응형

오늘의 5분 개발지식 라디오! 안녕하세요~ 오늘은 dependency injection, 의존성 주입에 대해서 이야기해보려고 합니다.

 

단어 자체가 직관적이지 않아서 저도 처음에 이해할때 많이 헷갈렸는데요, 의존성 주입이라는 용어에서부터 시작해보겠습니다.

 

일단 의존성이라는 단어부터 보면 좋을 것 같은데요, 의존성이란 말 그대로 하나의 코드가 다른 코드에 의존하는 상태를 뜻하는데요, 쉽게 말하자면 A라는 코드가 B라는 코드를 사용한다면, A는 B에 의존하고 있다, 혹은 의존성이 있다라고 말할 수 있습니다.

 

주입이라는 말은 말그대로 “넣어주다”라는 말인데요, 합쳐서 보면 의존성 주입이라는 용어는 의존성이 있는 코드, 객체를 넣어준다~라는 말입니다. 조금 더 실사용 예시로 풀어서 생각을 해보면, A라는 클래스가 B라는 클래스를 사용하고 있을때, 즉 의존하고 있을때, A 클래스에서 B클래스를 직접 생성해서 사용하는 것이 아니라 외부에서 B클래스의 인스턴스를 생성해서 주입해준다는 것입니다.

 

자 이렇게 의존성 주입이라는 용어를 이해했는데요, 의존성 주입이라는 단어를 사용할때에는 방금 설명한 가장 기초적인 개념외에도 추가적인 몇가지 개념들이 같이 포함되어있습니다.

 

첫번째로 Inversion of Control, 줄여서 IoC, 제어의 역전이라는 용어인데요, 의존성 주입이라는 오늘의 주제에 맞게 해석을 해보자면, 기존에는 의존성을 직접 모두 제어했다면, Inversion of Control이 발생하면 제어권이 역전되어있기때문에 반대로 직접 제어하지 않는다라고 생각하시면 됩니다. 그림으로 보시면 조금 더 이해가 빠르실텐데요,

 

조금 쉽게 이해가 되도록 대략적으로 그림으로 그려보았습니다.. 이전의 예시를 그대로 들고오자면 클래스A가 클래스B를 직접 참조하고 생성했던 것을 중간에 매개체를 하나 두고 그 매개체를 통해서 사용한다고 생각을 해보시면 기존의 화살표가 위에서 아래로 쭉 흐르던 것이 서로 마주보게 되어 제어를 나타내는 화살표의 방향이 역전된 것을 보실 수 있습니다.

 

이렇게 그림으로 보면 이해가 빠르긴 하지만 직접적으로 화살표 방향으로 생각하시지 마시고 개발자가 직접 의존성을 제어하던 것을 어떠한 매개체에게 제어권을 일임, 또는 뺏기게되어 더이상 제어의 주체가 개발자가 아니게되기 때문에 제어의 역전이 발생되었다고 보시면됩니다.

 

 

여기서 조금 전에 매개체라고 했던 것이 있는데요, 이 매개체를 IoC Container라고 합니다. 이 IoC Container는 개발자에게서 일임 받은 제어권을 사용하여 의존성을 관리하고, 인스턴스를 생성하여 주입해주고, 나중에는 메모리 해제하는 역할을 해줍니다. 이 IoC Container는 주로 프레임워크가 이 역할을 담당합니다. NestJS, Spring등과 같은 프레임워크들을 IoC Container를 포함하고 있습니다.

 

자 이제 정리를 해봅시다. 의존성 주입이라는 것은 IoC 컨테이너라는 매개체를 두고, 여기에 필요한 모든 모듈들을 등록해둡니다. 그리고 사용처에서 직접 생성하는 것이 아니라 필요할때에 IoC 컨테이더가 의존성이 있는 모듈을 주입해주는 방식입니다. 이 과정에서 의존하는 모듈의 생성과 해제, 주입 등 일련의 제어과정을 IoC 컨테이너 기능을 포함하는 프레임워크들에게 줌으로써 제어의 역전이 일어나게 됩니다.

 

 

 

이제 의존성 주입을 이해했으니 이 개념이 어떠한 장점들이 있는지를 이해해보겠습니다.

 

첫번째로 의존성이 줄어듭니다. 사용하는 클래스에서 직접 생성하는 것이 아니라 IoC container를 통해서 사용하기 때문에 그렇습니다. 의존성이 줄어든다는 것은 변화에 강하다는 말도 됩니다. 의존하고 있는 모듈의 라이프사이클을 전혀 신경쓰지 않기때문에 의존하고있는 모듈이 변경된다고 해서 이를 사용처에서 신경쓸필요가 없어집니다. 따라서 유지보수도 더 용이해집니다.

 

모듈의 생성과 삭제 등을 직접할 필요가 없기때문에 코드의 양도 감소합니다. 다이나믹하게 크게 차이나지는 않겠지만 작업속도도 빨라질 수 있겠죠.

 

또, 외부에서 의존성을 직접 주입받기때문에 테스트를 하기에도 용이합니다. 자신이 원하는 객체의 상태를 직접 세팅하여 주입할 수 있기때문에 내가 원하는 테스트 코드를 작성하기도 훨씬 더 수월합니다.

 

끝!








반응형
블로그 이미지

개발자_무형

,
반응형

Apache와 Nginx와 Tomcat 모두 유명한 Webserver framework들이다.

이 세가지 framework들을 간단하게 비교해보겠다. 비교는 Apache를 기준으로 하겠다.

 

Apache는 가장 오래된 Webserver framework들 중 하나로서, 모듈을 기반으로하는 구조를 가지고 있기때문에 커스터마이징이 자유롭다. 관리자들은 모듈들을 사용하여 새로운 기능들을 쉽게 도입할 수 있다. 또 .htaccess 파일을 편집하여 쉽게 설정을 바꿀 수 있다.

 

Apache Tomcat은 이름에서 알 수 있다시피, Apache를 개발한 개발사에서 만든 webserver이다. Tomcat의 가장 큰 차별점은 JAVA 어플리케이션을 위해서 만들어졌다는 점이다. Tomcat은 JVM이나 다른 JAVA 관련 라이브러리를 미리 로드해준다. 따라서 본인이 JAVA 어플리케이션을 호스팅하는 것이 아니라면 general-purpose HTTP server인 nginx나 apache를 사용하는 것이 더 적합하다.

 

Nginx는 2004년에 소개되었는데 이는 c10k problem을 해결하기위해 소개되었다. c10k problem이란 thread를 사용하여 user request를 처리하는 web server는 한번에 10000개 이상의 request를 처리할 수 없다는 문제이다. 이 문제를 해결하기 위해 nginx는 single thread로 master process가 여러개의 worker process들에게 request를 효율적으로 배분하는 방식으로 작동한다. 트래픽이 심한 웹사이트들은 nginx를 사용하는 것이 더 좋지만 그렇지 않은 경우에는 모듈을 임포트하여 쉽게 개발이 가능한 apache를 사용하는 것이 더 편리할 수 있다.

반응형
블로그 이미지

개발자_무형

,
반응형

사이트에 ssl을 적용하지 않으면 안되는 상황이 많아지면서 nginx에서 요청을 받아 ssl을 풀고 실제로 요청을 처리하는 docker container로 전달하는 경우가 많아졌습니다. 작업을 하다가 이와 같은 경우에 어떻게 해야되는지를 정리해보았습니다

 

아래는 docker container들을 관리해주는 docker-compose.yml입니다.ㄷ

//docker-compose.yml
version: '2'

services:
  janus-gateway:
    image: janus-gateway
    environment:
      - DOCKER_IP=${DOCKER_IP}
  nginx:
    image: nginx:1.14.2
    container_name: nginx
    ports:
      - "8088:8088"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - /path/to/ssl:/ssl

    depends_on:
      - janus-gateway

실제로 요청을 처리하는 컨테이너는 janus-gateway이고, nginx는 단순히 ssl을 해제하여 맞는 포트로 포워딩만 시켜줍니다.

 

nginx가 외부에서 접근하는 8088포트를 바인딩하여 자신의 컨테이너의 8088포트로 보내줍니다.

//nginx.conf
http {
  server {
    listen              8088;
    ssl on;
    server_name         YOUR_DOMAIN;
    ssl_certificate     /path/to/certificate;
    ssl_certificate_key /path/to/key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location / {
      proxy_redirect     off;
      proxy_set_header   Host $host;
      proxy_set_header   X-Real-IP $remote_addr;
      proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;

      proxy_pass http://janus-gateway:8088;
    }
  }
}

그 후 nginx에서 nginx.conf에서 nginx가 docker-compose.yml에서 설정하였듯 외부포트의 8088로 접근하는 요청을 내부 8088포트로 돌려줬기때문에 `listen 8088`을 사용하여 내부포트 8088에 바인딩해줍니다.

 

ssl을 해제한 이후 http://<docker 컨테이너 이름>:<실제 포트>로 전달하여줍니다.

 

여기까지는 간단한데, 헷갈렸던 점이 위의 docker-compose.yml같이 janus-gateway 컨테이너에 아무 port가 명시되어있지 않으면 외부로부터 요청을 받지 못하는 줄 알았는데, 내부포트는 전부 열려있고, 단지 자체적으로 외부 포트로의 바인딩이 되어있지 않은점입니다. 따라서 외부포트로부터의 요청을 하나도 받지 못하지만 위처럼 nginx가 내부의 8088번 포트로 포워딩해주는 요청들은 전부 받을 수 있습니다.

 

또, 처음에는 이 사실을 몰랐기에:

//docker-compose.yml
version: '2'

services:
  janus-gateway:
    image: janus-gateway
    ports:
      - "9088:8088"
    environment:
      - DOCKER_IP=${DOCKER_IP}
  nginx:
    image: nginx:1.14.2
    container_name: nginx
    ports:
      - "8088:8088"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - /path/to/ssl:/ssl

    depends_on:
      - janus-gateway
//nginx.conf
http {
  server {
    listen              8088;
    ssl on;
    server_name         YOUR_DOMAIN;
    ssl_certificate     /path/to/certificate;
    ssl_certificate_key /path/to/key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location / {
      proxy_redirect     off;
      proxy_set_header   Host $host;
      proxy_set_header   X-Real-IP $remote_addr;
      proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;

      proxy_pass http://janus-gateway:9088;
    }
  }
}

위와 같이 docker-compose.yml에서 janus-gateway를 9088번 포트에서 요청을 받아서 실제로 요청을 처리하는 8088번 포트로 포워딩해주고(왜냐하면 8088번으로부터 외부에서 들어오는 요청에는 ssl이 적용되어있어 nginx에서 ssl을 해제하여 포워딩해주어야합니다), 외부에서 8088번포트로 들어오는 요청은 nginx에서 받아서 janus-gateway 컨테이너의 9088번 포트로 포워딩해주었습니다.

 

해보니 당연히 작동이 되지않았습니다. janus-gateway에서 바인딩한 9088번포트는 해당 컨테이너가 돌아가고있는 물리적인 머신의 9088번 포트로 들어오는 요청을 컨테이너의 8088번포트로 연결해주는 것이었기때문에, nginx에서 컨테이너의 9088번포트로 보내주어도, 요청을 받지 못합니다.

반응형
블로그 이미지

개발자_무형

,
반응형

이 글은 본인이 공부를 목적으로 공식문서를 읽으며 정리/번역한 글로서 오역이 있을 수 있을 수 있습니다. 또한 본인이 이미 알고 있거나 불필요하다 느끼는 내용들은 누락될 수 있습니다.




Class-based view는 view를 함수가 아닌 파이썬 객체로 구현할 수 있는 하나의 방법이다. Class-based view는 function-based view를 대체하지는 않지만 그와 비교하여 차이점과 장점이 있다.



- HTTP method(GET, POST, etc)에 따른 코드를 conditional branching(IF)를 하지않고 별도의 메소드로 분리 할 수 있다.

- 객체 지향 테크닉들이 사용될 수 있다.



Using class-based views

from django.http import HttpResponse

def my_view(request):
    if request.method == 'GET':
        # <view logic>
        return HttpResponse('result')


가 아래로 대체될 수 있다.

from django.http import HttpResponse
from django.views import View

class MyView(View):
    def get(self, request):
        # <view logic>
        return HttpResponse('result')


장고의 URL resolver는 request와 인자들을 클래스가 아닌 호출가능한 함수에 보내기 때문에 class-based view는 as_view()라는 클래스 메소드를 사용하여 request를 받는다. 이 함수는 클래스 인스턴스를 생성하고 setup()을 호출하여 attribute들을 initialize하고 dispatch()를 호출한다. dispatch()는 request를 확인하여 GET,POST 등 해당하는 메소드에 전달하여 준다.







반응형
블로그 이미지

개발자_무형

,
반응형

Apache

Apache는 MPM(Multi Processing Module)이라는 방식으로 처리를 하는데 대표적인 방식으로 Prefork와 Worker방식이 있다.

Prefork MPM: 실행중인 프로세스를 복제하여 처리한다. 각 프로세스는 한번에 한 연결만 처리하고 요청량이 많아질수록 프로세스는 증가한다. 하지만 프로세스를 복제하는 것이므로 메모리영역까지 복제되어 동작하기 때문에 메모리 공유가 없어 안정적이다.

Worker MPM: Prefork방식이 1개의 프로세스가 1개의 스레드로 처리가 되었다면 Worker 동작방식은 1개의 프로세스가 각각 여러 쓰레드를 사용하게 된다. 쓰레드간의 메모리를 공유하며 PreFork방식보다 메모리를 덜 사용하는 장점이 있다.





Apache vs Nginx

Nginx의 가장 유명한 특징이라면 Event Driven방식이다. 따라서 어떠한 요청이 들어오면 어떤 동작을 해야하는지만 알려주고 다른 요청을 처리한다. 그러다보니 프로세스를 fork하거나 쓰레드를 사용하는 Apache와는 달리 CPU에 관계없이 모든 I/O를 전부 Event Listener로 미루기 때문에 흐름이 끊기지 않고 응답이 빠르게 진행되어 1개의 프로세스로 더 빠른 작업이 가능하게 될 수 있다. 이때문에 메모리측면에서 Nginx가 System Resource를 적게 처리한다는 장점이 있다고 한다.



https://taetaetae.github.io/2018/06/27/apache-vs-nginx/">https://taetaetae.github.io/2018/06/27/apache-vs-nginx/



Nginx는 Webserver이자 Reverse Proxy이다.



Proxy: client가 요청한 정보를 여러개의 서버로부터 받은 뒤 client에 반환한다.



Forward Proxy: 

client를 위한 proxy로써 아무 server와 통신할 수 있도록 해준다.



Reverse Proxy: 

server를 위한 proxy로써 서버 어플리케이션의 취약점들을 보완해줌으로써 아무 client와 통신할 수 있도록 해준다. 예를 들자면 과도한 로드를 버틸 수 있게 해주며(buffer 역할), 다양한 request들(http, https 등)을 처리할 수 있게 해준다.



https://en.wikipedia.org/wiki/Reverse_proxy">https://en.wikipedia.org/wiki/Reverse_proxy








반응형
블로그 이미지

개발자_무형

,
반응형

웹서버의 기본적인 역할은 정적(static)인 파일을 그대로 보내주는 것입니다. 그 말인즉슨, 뭔가 별도의 처리를 거치지 않고 존재하는 파일을 그대로 보내주는 역할을 합니다.



CGI(Common Gateway Interface)

웹서버에서 어플리케이션을 작동시키기 위한 인터페이스로서, 정적인 웹서버를 동적으로 기능하게 하기 위해서 등장하였습니다. 기존에는 외부프로그램이 필요한 리퀘스트가 들어오면 CGI를 통해 따로 프로세스를 fork하고 외부프로그램을 실행시켜 처리하였지만, 요즘에는 웹서버에 인터프리터를 내장시켜 내부적으로 처리한다. CGI Programming이란, Perl, C/C++를 사용하여 웹서버가 실행할 수 있는 프로그램을 작성하는 것이다.



WAS(Web Application Server)

웹서버가 동적으로 기능하면 WAS이다. 즉, Web Server + CGI가 WAS이다.



WSGI Server(middleware)

Web server와 WSGI를 지원하는 Web Application사이에서 동작하며 아래와 같은 일을 한다

    - 환경변수가 바뀌면 타겟 URL에 따라서 리퀘스트 경로를 지정해줌

    - 같은 프로세스에서 여러 어플리케이션과 프레임워크가 실행됨

WSGI vs CGI

CGI는 리퀘스트가 들어오면 CGI 프로토콜에 따라서 스크립트를 실행시킵니다. 서브프로세스를 fork하여 서브프로세스가 response를 작성하고 이를 웹서버로 보내면 웹서버가 이 response를 브라우저로 보냅니다. 대부분 CGI는 모든 리퀘스트마다 서브프로세스를 fork합니다.



WSGI는CGI 디자인 패턴에 기반한 인터페이스입니다. 가장 큰 차이점은 WSGI는 모든 리퀘스트마다 fork를 통해 서브프로세스를 띄우지 않으므로 느리지 않습니다.



https://brownbears.tistory.com/350">https://brownbears.tistory.com/350




왜 WSGI가 중요한가?


전통적인 Web Server는 파이썬 어플리케이션들을 이해하거나 실행시킬 수가 없다. 그래서 처음 Apache와 함께 사용하기 위해 mod_python이라는 모듈이 개발되었지만, 시간이 지남에 따라 보안 이슈와 개발 지연등의 이유로 새로운 방법이 필요하게 되었다. 따라서 파이썬 커뮤니티는 WSGI라는 모듈과 컨테이너들이 도입할 수 있는 표준 인터페이스를 작성하게 되었다.



WSGI의 목적

왜 Web server가 직접 application으로 포워딩하지 않고 WSGI를 거쳐야될까?

유연성

개발자는 웹서버를 바꿔도 웹 어플리케이션의 코드를 수정할 필요가 없다.

또, WSGI를 중간에 바꾸더라도 상관이 없다(Green Unicorn -> uWSGI)

즉, 사용자가 상황에 맞는 스택으로 쉽게 쉽게 바꿀 수 있다.

확장성

수천 건의 request를 처리하는 것은 framework의 역할이 아니라 WSGI server의 역할이다. WSGI 서버들은 request들을 어떻게 framework에 보낼지를 결정한다. 역할 분담은 확장성에 매우 중요하다.



https://www.fullstackpython.com/wsgi-servers.html">https://www.fullstackpython.com/wsgi-servers.html

반응형
블로그 이미지

개발자_무형

,
반응형

Ningx는 request가 가장 먼저 도착하는 곳이다.  Web application으로 보내져야만 하는 request들만을 보낸다.(필터링같은 느낌)



Gunicorn은 request를 웹 프레임워크가 이해하고 처리할 수 있는 형식으로 변환시켜 보낸다.





Nginx

Nginx는 Web server이자 Reverse Proxy이다. 아래는 Nginx가 잘하는 기능들 예시이다:

    - domain name routing

    - 정적 파일 보내기

    - 한번에 들어오는 많은 양의 request를 처리하기

    - 느린 client들 처리하기

    - 동적처리가 필요한 request들을 Gunicorn으로 보내기

    - SSL 끝내기

Nginx는 다음과 같은 일들은 할 수 없다:

    - Python web application 실행하기

    - request를 WSGI로 변역하기



Gunicorn

Gunicorn은 다음과 같은 일들에 특화되어 있다:

    - worker processes/threads 풀을 사용하여 코드 실행

    - Nginx가 보낸 request를 WSGI에 맞게 변환

    - 어플리케이션에서 보낸 WSGI response를 http response로 변환

    - request가 들어오면 실제로 파이썬 코드 실행하기

    - Gunicorn은 다양한 웹서버와 통신할 수 있음

다음과 같은 일은 할 수 없다:

    - 최전방에 나설 수 없다: DOS에 취약하다

    - SSL을 끝낼 수 없다(no https handling)




반응형
블로그 이미지

개발자_무형

,
반응형
1. Upstream Block
Upstream은 서버들의 묶음이다.
upstream backend {
    server 111.111.111:8000 weight=5;
    server 111.111.112:8000;
    server 111.111.113:8000;
}
위의 예시에서는 3개의 서버간에 load balancing이 되며 weight를 명시하게 되면 해당 서버에 더 많은 load를 배분할 수 있다. 위의 예시에서는 111.111.111서버가 다른 2서버에 비해 5배 많은 load를 할당받는다.

configuration 파일에서 server블럭에서는 ip주소로 호출하는 것이 아니라 upstream의 이름으로 호출한다. (예시에서 backend)

2. Server Block
Server Block은 하나의 가상 서버를 나타낸다. 따라서 여러개의 server block을 생성하게 되면 하나의 머신에서 여러개의 어플리케이션을 호스팅할 수 있다.

Nginx가 라우팅을 할때는 최우선 적으로 listen 옵션을 본다. listen 옵션은
    - address:port 숫자
    - ip주소 (포트80만 처리)
    - port 숫자 (해당 포트로 들어오는 모든 인터페이스)
    - unix socket 경로

Nginx는 오직 같은수준으로 명시된 listen이 여러개 있을 때만 server_name을 사용하여 비교한다.
server {
    listen 192.168.1.10;
    . . .
}
server {
    listen 80;
    server_name example.com;
    . . .
}
위의 예시에서 만약 example.com이 192.168.1.10의 80번 포트에 호스팅 되어 있다면, 항상 첫번째 블럭이 호출된다(Nginx는 불완전한 ip주소를 자동으로 채우므로 192.168.1.10:80으로 변환된다)








반응형
블로그 이미지

개발자_무형

,