spring boot api를 쿠버네티스로 deployment 해보기

IT 지식/Kubernetes|2019. 9. 11. 00:26

저번 글에서 기본적인 쿠버네티스 관련 개념과 자원에 대해 공부했다.

이제 실질적으로 api애플리케이션을 하나 만들어보고 배포까지 진행해보자.

 

로컬 이미지를 담을 registry 생성

쿠버네티스의 노드들은 외부와 연결되는 경우도 있지만 그렇지 못하는 환경도 많이 존재한다. 그럴 경우 이미지를 내려받을 수 없고 로컬에서만 만들어서 사용할 이미지를 등록할 registry가 필요하다.

# registry 이미지 가져오기
docker pull registry:latest

# 레지스트리 실행
docker run --name MyPrivateRegistry -d -p 5000:5000 registry

 

애플리케이션 생성

우선 spring boot gradle 프로젝트로 아무것도 만들지 않고 바로 빌드해서 사용해보자. 그 이유는 나중에 컨트롤러를 추가해서 엔드포인트가 늘어난 api로 이미지 버전을 변경해서 Rolling update 배포가 되는 것을 확인해보기 위해서이다.

그럼 만들어진 스프링 부트파일을 빌드하는 Dockerfile을 만들어보자.

FROM openjdk:8
ENV APP_HOME=/usr/app/
WORKDIR $APP_HOME
COPY ./build/libs/* ./app.jar
EXPOSE 8080
CMD ["java","-Dspring.profiles.active=development","-jar","app.jar"]

그리고 빌드를 진행하고 registry에 등록해놓자.

# 이미지 빌드 v1 tag
docker build -t demo/api:v1 .
docker image tag demo/api:v1 localhost:5000/demo/api:v1

# registry에 등록
docker image push localhost:5000/demo/api:v1

그리고 registry에서 해당 내용을 확인해보면 정상적으로 등록된 것을 알 수있다. http://localhost:5000/v2/_catalog

 

 

로드밸런서 서비스 실행

서비스의 경우 pod들의 외부와 통신을 담당한다는걸 저번시간에 공부로 알게되었다. 이제 pod를 배포하기전에 배포된 api 컨테이너들을 적절하게 로드밸런싱 해줄 서비스를 동작시켜보자. 

# node-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: node-svc
spec:
  selector:
    app: demo
  ports:
    - port: 80
      protocol: TCP
      targetPort: 8080
  type: LoadBalancer

v1 버전의 node-svc이름을 가지고 있고 demo 애플리케이션에 적용이 되며 tcp프로토콜을 가지고 타겟 포트가 8080을 가지고 있다.

kubectl create -f node-svc.yaml

지금은 endpoints에 아무것도 없다. 왜냐하면 아직 이 로드밸런서에 연결된 pods가 없기 때문이다. 그럼 이제 pods를 배포해서 연결해보자.

 

Pod 배포 (deployment)

그럼 pods를 배포해보자. 이미지는 아까 registry에 적용했던 api를 사용하고 버전은 우선 v1을 사용해보자.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-api
  labels:
    app: demo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: demo
  template:
    metadata:
      labels:
        app: demo
    spec:
      containers:
      - name: demo-api
        image: localhost:5000/demo/api:v1
        ports:
        - containerPort: 8080

그리고 로드밸런서 서비스를 보면 정상적으로 엔드포인트가 보이는걸 알 수있다.

그럼 minikube ip와 NodePort를 사용해서 한번 api에 붙어보자.

Minikube ip:nodeport

아무것도 만들지 않아서 whitelabel error가 발생한다. 

 

 

Rolling update pod교체

그럼 엔드포인트를 하나 만들어서 새로 image를 넣어보자.

@RestController
@RequestMapping("")
public class DemoController {

    @GetMapping("/test")
    public ResponseEntity test() {
        return ResponseEntity.ok("test");
    }
}

그리고 엔드포인트를 추가하고 나서 새로 이미지를 만들어준다.

docker build -t demo/api:latest .
docker image tag demo/api:latest localhost:5000/demo/api:latest
docker image push localhost:5000/demo/api:latest

그리고 deployments에 있는 demo-api 컨테이너의 api를 바꿔줘보자.

kubectl set image deployments/demo-api demo-api=localhost:5000/demo/api:v1
→ kubectl set image deplyments/{deployments 이름} {container 이름}={새로운 이미지}

그럼 이전 pod들이 꺼지고 새로운 이미지가 적용된 파드로 대체되는걸 볼 수 있다.

변경된 image가 잘 적용이 되었는지 확인해보면 잘나오는걸 확인할 수 있다.

 

굳굳 이렇게 쿠버네티스를 공부하고 실질적으로 한번 배포해봤다. 이제 이걸 wedul timeline에 한번 적용해보면서 더 운영을 해봐야겠다.

댓글()
  1. Favicon of https://lascrea.tistory.com BlogIcon Lascrea 2019.09.17 21:56 신고 댓글주소  수정/삭제  댓글쓰기

    기본적으로 Deployment는 Rolling Update를 사용하는걸로 알고 있어요!

  2. 2019.09.18 11:25 댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

Junit 정리 - 서비스 테스트 하기

web/Junit|2018. 5. 27. 00:44

전장에서 컨트롤러 테스트 방법을 공부하였다.
이번에는 서비스를 가지고 테스트 하는 방법을 설명한다. 


Assert 시리즈로 검증하기
- Assert 시리즈를 활용하면 해당 메소드의 결과값이 true인지 검증 뿐만 아니라 null 인지 등도 테스트를 진행할 수 있다.

 Assert로 설정한 대로 동작하지 않으면 테스트 도중 실패로 끝나기 때문에, 잘못된 결과 값이 나온다는 것을 확인하고 코드를 수정할 수 있다.

Assert 관련 메서드 종류는 다음과 같다.



Assert 메서드 종류
설명
assertArrayEquals(a, b)
배열 a와 b가 일치함을 확인한다.
assertEquals(a, b)
객체 a와 b가 일치함을 확인한다. (객체에 정의되어 있는 equals를 통해 비교한다.)
assertSame(a, b)
객체 a와 b 가 같은 객체임을 확인 한다. (객체 자체를 비교한다. == )
assertTrue(a)
조건 a가 참인지를 확인한다.
assertNotNull(a)
객체 a가 null인지 확인한다.



예제코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.wedul.wedulpos.user.test;
 
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
import com.wedul.wedulpos.user.dto.UserDto;
import com.wedul.wedulpos.user.service.UserService;
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"test-confing.xml"})
public class UserControllerTest {
    
    @Autowired
    UserService userService;
    
    UserDto dto;
    
    @Before
    public void setUp() throws Exception {
        dto = new UserDto();
        dto.setId("dbsafer");
    }
    
    @Test
    public void testUserController() throws Exception {
        assertTrue(userService.login(dto));
        assertFalse(!userService.login(dto));
        assertNull(userService.findPassword(dto));
    }
 
}
cs




기존에는 이런 방식으로 많이 테스트를 진행하였지만 요근래에는 mockito에서 제공하는 방식을 이용하여 테스트를 많이 진행한다고 한다.

이는 다다음장에서 공부해보자.




댓글()

UriComponents 클래스

web/Spring|2016. 12. 27. 00:47

UriComponents 클래스



UriComponents클래스는 Path query 해당하는 문자열들을 추가해서 원하는 URI 생성할 사용한다.

 

UriComponents uricomponets = UriComponentsBuilder.newInstance().path("/board/read").queryParam("bno",12).queryParam("perPageNum", 20).build();


logger.info(uricomponets.toString());



 

다음과 같이 지정하게 되면,

 

INFO : com.wedul.spring.UriComponentsTest - /board/read?bno=12&perPageNum=20


다음과 같이 설정되어 출력된다.

'web > Spring' 카테고리의 다른 글

Mybatis String을 ParameterType으로 넘길때  (0) 2018.05.27
spring에서 List 또는 Array 데이터를 Controller에서 받기  (0) 2018.05.27
UriComponents 클래스  (0) 2016.12.27
Mybatis의 동적 SQL  (0) 2016.12.27
STS의 github 연동  (0) 2016.12.21
Mybatis의 #{} 문법 사용방법  (0) 2016.12.21

댓글()

스프링 프레임워크의 기본적인 구성

web/Spring|2016. 12. 21. 22:22

1. VO 객체
-> 데이터를 담을 객체를 생성

public class BoardVO { 

private Integer bno; 
private String title; 
private String content; 
private String writer; 
private Date regdate; 
private int viewcnt; 

public Integer getBno() { 
return bno; 

public void setBno(Integer bno) { 
this.bno = bno; 

public String getTitle() { 
return title; 

public void setTitle(String title) { 
this.title = title; 

public String getContent() { 
return content; 

public void setContent(String content) { 
this.content = content; 

public String getWriter() { 
return writer; 

public void setWriter(String writer) { 
this.writer = writer; 

public Date getRegdate() { 
return regdate; 

public void setRegdate(Date regdate) { 
this.regdate = regdate; 

public int getViewcnt() { 
return viewcnt; 

public void setViewcnt(int viewcnt) { 
this.viewcnt = viewcnt; 
}

2. DAO 인터페이스
public interface BoardDAO { 

public void create(BoardVO vo) throws Exception; 

public BoardVO read(Integer bno) throws Exception; 

public void update(BoardVO vo) throws Exception; 

public void delete(Integer bno) throws Exception; 

public List<BoardVO> listAll() throws Exception; 


3. DAO 구현 클래스
@Repository 
public class BoardDAOImpl implements BoardDAO { 

@Inject 
private SqlSession session; 

private static String namespace="org.zerock.mapper.BoardMapper"; 

@Override 
public void create(BoardVO vo) throws Exception { 
session.insert(namespace+".create", vo); 


@Override 
public BoardVO read(Integer bno) throws Exception { 
return session.selectOne(namespace+".read", bno); 


@Override 
public void update(BoardVO vo) throws Exception { 
session.update(namespace +".update", vo); 


@Override 
public void delete(Integer bno) throws Exception { 
session.delete(namespace+".delete",bno); 


@Override 
public List<BoardVO> listAll() throws Exception { 
return session.selectList(namespace + ".listAll"); 


}

4. 데이터베이스에 사용할 쿼리를 정리한 mapper xml파일
<mapper namespace="org.zerock.mapper.BoardMapper"> 

<insert id="create"> 
insert into tbl_board(title, content, writer) 
values(#{title}, #{content}, #{writer}) 
</insert> 

<select id="read" resultType="org.zerock.domain.BoardVO"> 
select 
bno, title, content, writer, 
regdate, viewcnt 
from 
tbl_board 
where bno = #{bno} 
</select> 

<update id="update"> 
update tbl_board set title=#{title}, content=#{content} 
where bno=#{bno} 
</update> 

<delete id="delete"> 
delete from tbl_board where bno= #{bno} 
</delete> 

<select id="listAll" resultType="org.zerock.domain.BoardVO"> 
<![CDATA[ 
select  
bno, title, content, writer, regdate, viewcnt 
from  
tbl_board 
where bno > 0 
order by bno desc, regdate desc 
]]> 
</select> 
</mapper>

5. root-context.xml
-> Bean 관리

<!-- 스프링에 빈으로 등록하기, 패키지 자동인식 --> 
<context:component-scan base-package="org.zerock.persistence"></context:component-scan> 

<bean id="dataSource" 
class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 
<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property> 
<property name="url" 
value="jdbc:log4jdbc:mysql://192.168.25.57:3306/book_ex"></property> 
<property name="username" value="root"></property> 
<property name="password" value="ddd00"></property> 
</bean> 

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 
<property name="dataSource" ref="dataSource"/> 
<property name="configLocation" value="classpath:/mybatis-config.xml"></property> 
<property name="mapperLocations" value="classpath:mapper/**/*Mapper.xml"></property> 
</bean> 

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache"> 
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg> 
</bean> 
</beans>

6. 테스트 클래스
@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/**/*.xml"}) 
public class BoardDAOTest { 
private static final Logger logger= LoggerFactory.getLogger(BoardDAOTest.class); 

@Inject //BoardDAO에 대한 의존성 주입 
private BoardDAO dao; 

@Test // 글 생성 테스트 
public void testCreate() throws Exception { 
BoardVO board = new BoardVO(); 
board.setTitle("input the new Text"); 
board.setContent("input the new Text Content"); 
board.setWriter("user00"); 
dao.create(board); 


@Test 
public void testRead() throws Exception { 
logger.info(dao.read(1).toString()); 


@Test 
public void testUpdate() throws Exception { 
BoardVO board = new BoardVO(); 
board.setBno(1); 
board.setTitle("Update Title"); 
board.setContent("Update Content"); 
dao.update(board); 


@Test 
public void testDelete() throws Exception { 
dao.delete(1); 
}

출처 
남가람북스
코드로 배우는 스프링 웹 프레임워크

'web > Spring' 카테고리의 다른 글

STS의 github 연동  (0) 2016.12.21
Mybatis의 #{} 문법 사용방법  (0) 2016.12.21
Spring의 UTF-8 처리 필터 등록  (0) 2016.12.21
스프링 프레임워크의 기본적인 구성  (0) 2016.12.21
Mybatis 관련 정리  (0) 2016.12.21
typeAliases 사용방법  (0) 2016.12.21

댓글()