728x90
반응형

쿠버네티스를 macOs m1 환경에서 실행하기

https://velog.io/@pinion7/macOs-m1-환경에서-kubernetes-시작하기 를 참고하였습니다.

싱글 노드 클러스터 배포판 → minikube를 사용함

kubernetes로 배포 실습

  1. kubectl로 kubernetes 클러스터 생성

kubectl apply -f wordpress-k8s.yml

  1. 생성된 클러스터와 여러 컴포넌트 리스트 확인

kubectl get all

  1. port-forwarding을 해주어야 한다

kubectl port-forward service/wordpress 4000:80

  1. 위에건 로컬로 실행하는 것이고 minikube NodePort를 통해서도 접근할 수 있다.
    minikube service wordpress

  • 커맨드를 입력하면 일단 최종 target port가 보인다. 최종적으로 도달하고자 하는 오브젝트는 Pod이기 때문에, 이건 Pod에 붙는 Port를 의미한다.
  • 다만 클러스터 내부에 있는 Pod를 외부에서 접근할 수는 없기 때문에, NodePort가 외부 트래픽을 받을 수 있는 외부 노출 URL(192.168.49.2:32374)을 만들어 준 것이다. 이렇게 NodePort를 통하면 Pod에 트래픽이 전달될 수 있다.
    • 원래는 192.168.49.2:32374로 접근이 가능해야 하는데, 말했듯 m1 운영체제의 한계로 불가능하다.
  • 그래서 NodePort url을 로컬에 포워딩 할 수 있어야 하는데, minikube service 커맨드가 그걸 가능하게 해준다.
    • 보다시피 포워딩된 로컬 url(127.0.0.1:63828)이 제공된 것이다. (참고로 로컬 포트는 랜덤하게 지정된다)
  • minikube 커맨드는 동시에 해당 로컬 url로 browser를 아래처럼 자동으로 띄워주기도 한다.

4) 실습으로 살펴본 docker와 kubernetes

  • yml 파일로 본 docker와 kubernetes의 구조적 차이
    • docker: 구조가 간단하다. docker의 기본 단위는 컨테이너다.docker-compose.yml을 보면, 하나의 Service 안에 컨테이너가 모두 포함되어 있다. 즉, compose 진행을 위해 필요한 명세는 2개 이상의 컨테이너와 포트 매핑, 기타 환경변수 정도이다.
    • kubernetes: 구조가 복잡하다. kubernetes의 기본 단위는 컨테이너가 아닌 클러스터이다. 클러스터는 최소 1개 이상의 노드로 구성이 되고, 노드에 띄워지는 워크로드를 구성하는 요소들을 오브젝트라고 칭한다. wordpress-k8s.yml 파일을 보면, wordpress 워크로드와 mysql 워크로드 각각에 Deployment & Service 리소스로 만든 오브젝트가 존재한다. 그리고 그 안에는 메타데이터, 컨테이너, 환경변수, 포트 등이 상세하게 명세되어 있다.
  • docker와 kubernetes에 대한 나의 이해..
    • 구조 파악을 통해 확인할 수 있는 차이는, 배포에 있어 필요한 명세나 스케일이 확연히 다르다는 것이다. 깊게 하나하나 공부하지 않은 현 상태에서 아직 명확한 차이를 짚을 수는 없다.
    • 또한 분명한 건 kubernetes를 사용한다고 하여 docker를 배제하는 것은 아니다. minikube만 봐도 알 수 있듯이, 분명 kubernetes 내부에서 docker에 대한 의존성을 가지고 있다. kubernetes 자체적으로 docker를 nesting해서 사용하는 것이 아닐까 싶다.
    • 명확한 kubernetes의 구조 및 철학을 이해하려면 일단 클러스터와 주요 골격이 되는 노드 및 각종 컴포넌트, 오브젝트 등의 개념부터 알아야 할 것으로 보인다. 이는 추후 블로깅에서 자세히 다뤄보겠다.쿠버네티스 환경에서 애플리케이션을 배포하는 과정
    • sudo docker build --build-arg DEPENDENCY=build/dependency -t jakeheon/githubaction --platform linux/amd64 .
    • 컨테이너 이미지 생성컨테이너 레지스트리로 이미지 업로드쿠버네티스에서 컨테이너 이미지를 바탕으로 Deployment 생성
    • 스프링 어플리케이션을 실행시키기 위해서는 gradle wrapper를 사용해 jar 파일을 만든 후 실행시켜야 합니다.
    • 스프링 프로젝트를 Container 이미지 파일로 만들기 위해서는 다양한 방법들이 존재 합니다.용어 정리 및 기타
    • kubectl apply -f deployment.yaml kubectl get deployment kubectl get pod
    • 쿠버네티스에 컨테이너화 된 어플리케이션을 배포하기 위해서는 디플로이먼트를 생성해야합니다.
    • 파드 : 쿠버네티스 클러스터에서 실행되는 최소 단위로 독립적인 공간과 사용 가능한 IP를 지니고 있음
    • 레플리카셋 : 명시된 동일 파드 개수를 항상 실행시켜주는 것을 보장 시켜주는 쿠버네티스 오브젝트
    • 디플로이먼트 : 파드와 레플리카셋에 대한 선언적인 업데이트를 제공하는 쿠버네티스 오브젝트로 오늘날에는 쿠버네티스 상에서 컨테이너를 배포할 때 디플로이먼트를 사용
728x90
반응형
728x90
반응형

웹 브라우저가 HTTP 요청을 하게 되면 HTTP Header, Body 등 웹 브라우저에서 보낸 다양한 정보가 서버로 넘어가게 된다.


웹 브라우저가 HTTP 요청을 하면 해당 메시지를 서버가 받아서 메시지를 파싱도 하고, URL이 뭔지 판단도 하고, content-type도 넘어오니까, 원하는 정보를 몇 글자인지 보고 파싱해서 사실하면 로직을 처리할 수 있다. 거기에 response를 client에게 전달해 주면 된다. 그러나, 이런 작업은 반복적인 작업도 많고, 단순한 비즈니스 로직 하나를 처리하기 위해서도 귀찮은 일이 생긴다. 이걸 더 편리하게 해 주기 위해서 나온 게 서블릿이다.

 

서블릿은 이런 HTTP 작업을 단순화해 준다. HTTP 요청 정보를 편리하게 사용할 수 있는 HttpServletRequest 객체와 HTTP 응답 정보를 편리하게 사용할 수 있는 HttpServletresponse 객체를 제공해 줘서, 개발자는 HTTP 스펙을 쉽게 사용할 수 있게 되는 것이다.

서블릿(Servlet)

  • 자바 웹 애플리케이션 개발을 위한 기술로, 동적인 웹 콘텐츠를 생성하는 데 사용
  • HTTP 요청과 응답을 처리하기 위한 자바 클래스
  • 서블릿 컨테이너(예: Apache Tomcat)에서 실행되며, 요청에 대한 처리와 스레드 관리를 담당

HTTP 요청과 응답을 서블릿이 어떻게 처리해주는지 알아보자.

  1. HTTP 요청이 들어오면 WAS는 Request 객체랑 Response 객체를 새로 만들어서 서블릿 객체를 호출한다.
  2. 그럼 Request 객체에서 HTTP로 어떤 요청이 들어왔는지 확인하고
  3. 해당 로직을 처리한 다음(해당 로직을 처리 주는 것이 바로 서블릿 객체이다)
  4. Response 객체에 Client에게 전달해야 할 응답 정보를 담아서 전달해 준다. 이때 WAS가 HTTP 응답 정보를 생성해 준다.
  5. Client가 해당 정보로 렌더링 해서 이제 보여준다.

서블릿 컨테이너

WAS 안에는 서블릿 컨테이너라는 게 있는데 서블릿 컨테이너가 서블릿 객체를 자동으로 생성해 준다. 호출도 해주고, 관리까지(예를 들면 WAS 종료될 때 서블릿도 같이 종료시키는) 해준다. 톰캣은 서블릿 컨테이너의 종류 중 하나이다.

즉, WAS는 웹 서버와 서블릿 컨테이너로 구성된다고 생각하면 된다. 사실 경계가 모호하기 때문에 종류로 생각하는 게 편하다. 

웹 서버는 nginx와 apache가 해당하는 것이고, 서블릿 컨테이너는 apache tomcat(아파치 톰캣)이 해당한다.

 

서블릿 컨테이너의 장점 중 하나는 싱글톤으로 관리된다라는 사실이다. 싱글톤은 객체를 하나만 생성해놓고 서로 공유해서 사용하는 디자인 패턴을 의미한다. 만약 사용자가 10000명 이상으로 온다면 비즈니스 로직으로 개게를 엄청나게 만들어야 되는데, 싱글톤을 사용하면 하나를 공유해서 사용하면 되므로 딱 봐도 이점이 느껴진다.

 

그렇다고, request랑 response를 다 공유하는 건 아니다. 웹 브라우저에서 사용자마다 전달해오는 데이터는 당연히 다를 것이다.

(내 아이디 비번과 다른 사람의 아이디 비번이 다른 것과 마찬가지이다)

 

request, response 객체는 따로 생성하지만 비즈니스 로직인 서블릿은 공유해서 사용하는 것이다. 항상 재사용한다. 그래서 모든 고객 요청은 동일한 서블릿 객체 인스턴스에 접근하게 된다. (그래서 공유 변수 사용에 주의해야 한다)

 

또 다른 중요한 장점은 서블릿 컨테이너는 멀티 스레드 처리도 지원을 한다는 사실이다. 

 

위에 그림처럼 request가 생기면 서블릿을 호출하게 되는데, 그럼 서블릿은 누가 호출하는 건가??

쓰레드가 호출하게 된다. 스레드는 애플리케이션의 흐름이라고 알고 있는데, 말 그대로이다. 스레드가 아무 작업을 안 하고 쉬고 있다가, 만약에 요청이 들어오면 스레드를 할당해 줘서 서블릿을 호출해 주는 시스템인 것이다. 요청하고 응답까지 다 하면, 스레드는 다시 휴식하게 되는 것이다. 

 

다중 요청이 들어올 때도, 쓰레드가 여러 개면 해결이 될 것 같다. 요청마다 스레드를 생성해 주면 되는 것이다. 이렇게 생각했는데 이 방법은 당연히 단점이 있다. 

장점으로는 동시 요청을 처리하면서, 하나의 쓰레드가 지연되어도, 나머지 스레드는 정상 동작하기 때문에 상관이 없다.

그렇지만 쓰레드는 생성 비용이 매우 비싸고, 고객의 요청이 올 때마다 새로운 스레드를 생성하면 응답 속도가 늦어지고, 고객 요청이 너무 많이 오면 결국 임계점을 넘기면 서버가 죽을 수도 있는 것이다. 

 

근데 WAS는 멀티 스레드를 다 처리해주기 때문에 멀티 스레드 관련 코드를 신경 안 써도 된다.

 

웹 서블릿의 동작 원리

@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        System.out.println("HelloServlet.service");
        System.out.println("request = " + request);
        System.out.println("response = " + response);

        String username = request.getParameter("username");
        System.out.println("username = " + username);

        response.setContentType("text/plain");
        response.setCharacterEncoding("utf-8");
        response.getWriter().write("hello " + username);


    }
}

위의 코드를 한번 살펴보자. @WebServlet 어노테이션을 선언해주면 이제 서블릿 컨테이너가 관리를 해주게 되는 것이다. 

 

서블릿은 클라이언트의 요청을 HTTP 형태로 받는다. @WebServlet 어노테이션의 Parameter로는 name과 urlPatterns를 받는다.

단어 그대로, name은 서블릿의 이름이고, urlPattern은 클라이언트에서 요청하는 URL이다. 이 둘을 매핑해 주는 것이다.

만약 HTTP를 통해 매핑된 URL이 호출되면 서블릿 컨테이너는 다음 메서드를 실행한다.

protected void service(HttpServletRequest request, HttpServletResponse response);

위의 함수 파라미터 그대로 해석하면 request에는 클라이언트 요청, response는 서블릿이 이제 대답을 준다. 해당 함수를 어떻게 커스터마이징하고 조작하는 역할을 이제 서블릿 컨테이너 안의 서블릿이 해주는 것이다. 그 조작을 service함수 내에서 하면 되는 것이다.

그래서 서블릿의 이름과 urlPattern은 중복되게 하면 안된다. 

 

그럼 위의 코드에서 request 객체와 response 객체에는 어떤 것들이 오는지 로그를 찍어보자.

apache.catalina는 tomcat쪽 라이브러리이다.(서블릿 컨테이너 쪽 라이브러리)

HttpServletRequest나 HttpServletResponse는 interface이다.

이제 Tomcat이나 Jetty 등등 이런 WAS 서버들이 서블릿 표준 스펙을 구현하는 것이다. 여기 찍히는 것은 Tomcat의 구현체라고 볼 수 있다.

쿼리 파라미터

서블릿의 장점 중 하나는 쿼리 파라미터를 아주 쉽게 읽을 수 있게 해 준다는 사실이다.

request로 query parameter를 전송하면 request 객체에서 getParameter를 사용하면 해당 query 파라미터를 읽어낼 수 있다.

응답 메시지를 보여줄려면 response에 넣어줘야 한다.

setContentType를 text/plain으로 준다면 단순 문자열을 전송해 주는 것이다.

setCharacterEncoding은 utf-8로 둔다.

그럼 여기까지 정보는 Header에 들어가게 된다.

나머지는 response.getWriter().write() 를 통해서 보내준다.

 

모든 HTTP 요청 응답 정보들을 보고 싶으면 아래와 같이 설정하면 된다. (application.properties)

logging.level.org.apache.coyote.http11=debug

지금까지의 동작 과정

  1. 스프링부트로 프로젝트를 생성 후 실행 → 스프링부트 안에 있는 내장 톰캣 서버를 띄워준다.
  2. 톰캣 서버는 내부에 서블릿 컨테이너를 가지고 있다.
  3. 서블릿 컨테이너를 통해서 내부에 서블릿을 다 생성해준다. → HelloServlet 생성
  4. GET /hello?username=kim HTTP/1.1 Host: localhost:8080 웹브라우저가 이런 형식으로 HTTP 메시지를 만들어서 서버에 요청을 한다.
  5. 서버는 Request, Response 객체를 만들어서 HelloServlet의 Service 메서드를 호출하면서 response 객체를 만들어서 웹 브라우저에게 전달해 준다.
  6. HTTP 응답에서 Content-Length는 웹 애플리케이션 서버가 자동으로 생성해 준다. (WAS가 해줌)

HttpServletRequest의 역할

위에서 말했듯이 HTTP 요청 메시지를 개발자가 직접 파싱 하기 어려우니까, 서블릿은 개발자가 HTTP 요청 메시지를 편리하게 사용할 수 있도록 개발자 대신에 HTTP 요청 메시지를 파싱 한다. 그리고 그 결과를 HttpServletRequest 객체에 담아서 제공한다.

추가적인 역할

  1. 임시 저장소 기능 : HTTP 요청이 시작부터 끝날 때까지 유지되는 임시 저장소 기능
    1. 저장 : request.setAttribute(name, value)
    2. 조회 : request.getAttribute(name)
  2. 세션 관리 기능 : request.getSession(create: true)

클라이언트에서 HTTP를 통해 서버로 전달하는 방법

  1. GET - 쿼리 파라미터
    1. 검색, 필터, 페이징 등에서 많이 사용하는 방식
    2. 서버는 HttpServletRequest를 가지고 정보를 쉽게 가져올 수 있다
  2. POST - HTML Form
    1. content-type: application/x-www-form-urlencoded
    2. 메시지 바디에 쿼리 파라미터 형식으로 전달 → 똑같이 request.getParameter로 꺼낼 수 있다!
    3. 클라이언트 입장에서는 차이가 있는데, 서버 입장에서는 둘의 형식이 동일한 것이다
    4. 회원가입, 상품 주문에 사용
  3. HTTP message body에 데이터를 직접 담아서 요청
    1. HTTP API에서 주로 사용, JSON형식으로 사용
    2. POST, PUT, PATCH
    3. String으로 주고 받는 방식(잘 쓰이지 않음)
@WebServlet(name = "requestBodyStringServlet",urlPatterns = "/request-body-string")
public class RequestBodyStringServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletInputStream inputStream = request.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        System.out.println("messageBody = " + messageBody);
        response.getWriter().write("OK");

    }
}
  • getInputStream을 하면 메시지 body의 내용을 byte코드로 바로 얻을 수 있다
  • byte 코드를 String으로 바꿔야 하는데 Spring에 StreamUtils라는 유틸리티 클래스를 사용하면 된다.
    • 인코딩 정보를 알려줘야 한다
  • 지금 이건 String 형식으로 주고 받는 것이다.
  • JSON으로 주고 받는 방식이 보통 사용되는데 코드는 아래와 같다.
@WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-body-json")
public class RequestBodyJsonServlet extends HttpServlet {

    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // request
        ServletInputStream inputStream = request.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

        System.out.println("messageBody = " + messageBody);
        HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
        System.out.println("helloData.username = " + helloData.getUsername());
        System.out.println("helloData.age = " + helloData.getAge());

    }
}
// HelloData
@Getter @Setter
public class HelloData {

    private String username;
    private int age;

}
  • 스프링은 JSON 라이브러리를 기본적으로 jackson이라는 것을 사용한다.
  • ObjectMapper를 선언하고 readValue를 통해서 class타입으로 변환할 수 있다. 여기서는 HelloData로 변환을 하였다.
  • 나중에는 MVC가 제공하는 기능을 통해서 더 쉽게 JSON을 파싱할 수 있다

서블릿과 자바로 동적으로 변경해야 하는 부분을 보여주고 싶을 때 템플릿 엔진을 사용할 수 있다. 템플릿 엔진을 사용하면 HTML 문서에서 필요한 곳만 코드를 적용해서 동적으로 변경할 수 있다.

 

템플릿 엔진에는 JSP, Thymleaf, Freemarker, Velocity 등이 있다. 이전에는 JSP가 많이 사용되었었지만, 스프링은 현재 Thymeleaf를 사용하는 걸 추천한다.

 

단순히 서블릿만을 사용하기에는 HTML을 다루기가 너무 힘들기 때문에 JSP나 타임리프 같은 것들을 사용하게 된다.

 

JSP(JavaServer Pages)

  • 서블릿을 기반으로 하는 웹 페이지 개발을 위한 기술
  • HTML 코드 내에 자바 코드를 삽입하여 동적인 웹 콘텐츠를 생성할 수 있다
  • JSP 파일은 서블릿으로 변환되어 서블릿 컨테이너에서 실행

서블릿과 JSP의 한계

분리되어 있지 않다. 이게 제일 핵심 단점이다. 서블릿과 JSP를 사용하다 보면 비즈니스 로직이랑 화면(View)랑 분리되지 않아서 지저분하고 나중에 코드를 볼 때도 복잡하여 유지보수가 어렵다. 


그래서 등장하게 된 것이 MVC 패턴이다.

MVC 패턴

MVC 패턴은 컨트롤러와 뷰라는 영역으로 서로 역할을 나눈다. MVC 용어에서 알 수 있듯이 여기서 V는 view이고, C는 Controller이다.
M은 모델인데, 모델은 뷰에 출력할 데이터를 담아두고 뷰가 필요한 데이터를 모두 모델에 담아서 전달한다. 덕분에 뷰는 비즈니스 로직이나 데이터 접근 같은 걸 몰라도 되고, 화면만 렌더링 하면 되는 것이다.

 

그리고, Service로도 이후에 나눈다. Controller에 비즈니스 로직을 둘 수도 있지만 이렇게 되면 또 컨트롤러가 부담을 많이 지는 문제가 발생하기 때문에 Service 계층을 따로 나누어서 처리한다. Controller는 서비스 계층을 호출하는 역할만 하면 된다.

 

MVC 패턴과 관련된 프레임워크도 많았었는데, 어노테이션 기반의 스프링 MVC가 나오면서 거의 정리되었다. 

 

MVC 패턴의 한계

MVC 패턴의 한계라기보다는 MVC 패턴을 개선하는 방안이다. MVC 패턴은 요청하는 부분과 응답하는 부분, 즉 request response 코드가 controller마다 따로 구성되어 있다. 중복된 코드는 항상 개선점에 해당한다. 그래서 FrontController라는 것이 고안되었다.

FrontController 패턴

  • Front Controller 패턴은 소프트웨어 디자인 패턴 중 하나로, 웹 애플리케이션에서 중앙 집중화된 컨트롤러 역할을 수행하는 컴포넌트
  • 이 패턴은 클라이언트의 모든 요청을 하나의 진입점으로 보내고, 해당 요청을 처리하기 위해 적절한 핸들러(컨트롤러)로 라우팅 하는 구조를 가지고 있다
  • Front Controller를 제외한 나머지 Controller는 서블릿을 사용하지 않아도 된다.

스프링 웹 MVC에서 보면 FrontController를 사용한다. 우리가 DispatcherServlet으로 알고 있는 것이 FrontController 패턴으로 구현되어 있다. 프런트 컨트롤러 서블릿이 클라이언트 요청을 다 받고 프런트 컨트롤러가 요청에 맞는 컨트롤러를 찾아서 호출하는 즉, 입구를 하나로 만드는 전략이다.

개선점

위의 서블릿 코드에서 override 된 service코드를 보면 HttpServletRequest랑 HttpServletResponse를 계속하여 쓰게 되는데, 해당 코드가 컨트롤러 입장에서는 필요 없다. Controller는 View로 객체를 전달하기만 하면 되는데, 해당 객체를 Model 객체라고 한다. 그럼 이제 우리가 아는 구조가 나오게 된다.

전형적인 Front Controller 패턴의 구조는 다음과 같다:

  1. 클라이언트는 요청을 Front Controller에게 전송
  2. Front Controller는 요청을 받아 해당 요청을 처리할 적절한 핸들러(컨트롤러)로 라우팅
  3. 핸들러는 요청을 처리하고 필요한 작업을 수행한 후 응답을 생성
  4. Front Controller는 핸들러로부터 받은 응답을 클라이언트에게 전송

뷰 리졸버 (View Resolver)

뷰 리졸버(View Resolver)는 컨트롤러가 처리한 결과를 어떤 뷰로 보여줄지를 결정하는 역할을 한다.
뷰 리졸버는 뷰의 논리적인 이름을 실제 뷰 객체로 변환하여 반환하는 기능을 담당한다.

웹 애플리케이션은 MVC (Model-View-Controller) 아키텍처를 따르는데, 이 구조에서 컨트롤러는 요청을 처리하고 필요한 작업을 수행한 후 결과를 뷰에 전달한다. 뷰 리졸버는 컨트롤러가 반환한 뷰의 논리적인 이름을 실제 뷰 객체로 매핑하여 클라이언트에게 보여줄 뷰를 결정하게 되는 것이다.

Spring MVC

  • Spring Framework에서 제공하는 웹 애플리케이션 개발을 위한 모듈
  • Model-View-Controller(MVC) 아키텍처 패턴을 기반으로 한다
  • 요청을 처리하는 컨트롤러(Controller), 데이터를 처리하는 모델(Model), 화면을 표현하는 뷰(View)로 구성
  • 애노테이션 기반의 설정과 느슨한 결합을 통해 유연하고 효율적인 웹 애플리케이션 개발을 지원
  • 요약하면, 서블릿은 자바 기반의 웹 애플리케이션 개발을 위한 기술이고, JSP는 서블릿을 기반으로 동적인 웹 페이지를 개발하기 위한 기술
  • Spring MVC는 Spring Framework에서 제공하는 모듈로, 웹 애플리케이션 개발을 위한 MVC 패턴을 구현하는 방식
  • Spring MVC는 서블릿과 JSP를 기반으로 하며, 개발자에게 편의성과 유연성을 제공하여 웹 애플리케이션 개발을 보다 쉽게 할 수 있도록 도와준다

 

 

참고 : 인프런 김영한 님 스프링 MVC 편

728x90
반응형

+ Recent posts