콘텐츠로 이동

2022 06 30

2022-06-30

도커로 배포하기

  • 참고: https://subicura.com/2016/06/07/zero-downtime-docker-deployment.html
  • 어떻게 배포하는 것이 좋은가?
    • 쉽고 관리하기 편한 방법을 선택하자
    • 자동으로 배포하자
    • 하루에도 여러번 배포하자
    • 배포 중 서비스가 중단되는 일이 없도록 하자
    • 모든 서비스는 도커를 이용해 컨테이너 형태로 표준화하여 배포하자
    • 테스트 서버에 동일한 방법으로 배포하고 테스트하자
    • 시작은 서버 한대지만 나중에 여러대로 확장되었을때를 대비하여 설게를 고민하자
  • Why Docker?
    • 새로운 서버에 서비스를 동작시키려면 굉장히 많은 작업 필요
    • 백엔드 언어에 귀속되는 다양한 익스텐션 설치 필요
    • 서버 한 대 추가된다고 똑같이 세팅하기가 참으로 어려움
    • 버전 업데이트되면 그것도 그것대로 골때림
  • 도커를 통한 배포가 갖는 특징
    1. 확장성
      • 이미지만 만들어둔다면 컨테이너는 띄우기만 하면 됨
      • 다른 서버로 서비스 옮기거나 새로운 서버에 서비스 하나 더 띄우는건 docker run 명령어면 충분
      • 개발서버 띄우기도 편하고 테스트서버 띄우기도 간편
    2. 표준성
      • 도커 사용하지 않는 경우 다른 언어로 만든 서비스들의 배포 방식은 제각각 다르다
      • 컨테이너라는 표준으로 서버를 배포해야 배포 과정이 동일해짐
    3. 이미지
      • 이미지에서 컨테이너를 생성하기에 반드시 이미지 만드는 과정 필요
      • 이미지 저장할 공간 필요
      • 해당 이미지를 distribution에 저장하고 운영서버에서 이미지 불러옴
    4. 설정
      • 설정은 보통 환경변수로 제어
      • MYSQL_PASS=password와 같이 컨테이너 띄울때 환경변수 같이 지정
      • 하나의 이미지가 환경변수에 따라 동적으로 설정파일 생성하도록 만들어야 함
    5. 공유자원
      • 컨테이너는 삭제 후 새로 만들면 모든 데이터가 초기화됨
      • 업로드 파일을 외부 스토리지와 링크하여 S3 같은 별도 저장소 필요
      • 세션이나 캐시를 파일로 사용한다면 redis 외부로 분리

컨테이너로 배포 vs 프로세스로 배포

  • 참고: https://zzang9ha.tistory.com/360
  • 굳이 컨테이너?
    • 어플리케이션이 동작하는 환경 자체를 개발 환경과 운영 환경을 동일하게 구성 가능
    • 해당 서비스에 대한 트래픽 증가시 보다 효율적으로 서비스 확장하기 좋음
  • 도커파일 예시
    FROM openjdk:11
    EXPOSE 8080
    ADD build/libs/msa-0.0.1.jar msa-0.0.1.jar
    ENTRYPOINT ["java","-jar","msa-0.0.1.jar"]
    
    FROM openjdk:8-jdk-alpine
    ARG JAR_FILE
    COPY ${JAR_FILE} app.jar
    COPY application-db.yml /application-db.yml
    ENTRYPOINT ["/bin/sh","-c","java -Dspring.config.location=classpath:/application-${IDLE_PROFILE}.yml,/application-db.yml -Dspring.profiles.active=${IDLE_PROFILE} -jar ./app.jar"]
    
  • 구축 이후 Build한 이미지를 컨테이너에 올려보자
    docker run -p 8080:8080 msa-0.0.1
    
  • 이후에 배포 스크립트를 작성해서 인스턴스에서 돌린다!
  • dockerhub을 통한 배포
    • 과정
      • Dockerfile을 build해서 docker image 파일 생성
      • docker image 파일을 dockerhub에 push
      • 서버에서 docker hub에 존재하는 docker image를 pull 해옴
      • docker run 명령어를 통해 docker image 파일 실행
    • 개발환경에서 포장한 컨테이너 그대로 운영서버에서 돌릴 수 있다!

GraphQL

  • GraphQL 유튜브

    • GraphQL = Query Language
    • Query를 보내서 정확히 필요한 데이터만 받아오는 것
    • 요청
      {
          upcoming {
              title
          }
          nowPlaying {
              title
              popularity
          }
      }
      
    • 응답
      {
          "upcoming": [
              {"title": "Thor"},
              {"title": "이별할 준비"},
              {"title": "탑건"}
          ],
          "nowPlaying": [
              {"title": "Thor"},
              {"title": "이별할 준비"},
              {"title": "탑건"}
          ]
      }
      
    • POST 요청 하나로도 유연하게 주문이 가능함
      • CUD 모두 POST 요청
      • U,D는 함수명으로 뚝딱 요청
    • 필요한 정보들을 요청 body 에다가 다 적어서 보내면 쿼리 조합해서 한번에 응답해 줌
    • 같은 API 서버를 쓰더라도, 사용자마다 요청이 다르고 PC/안드로이드/아이폰 등 기기 마다 다르면 유리
    • 여러 depth들의 정보들을 한번에 받아올 수 있지
  • GraphQL 글
    • API에 대한 새로운 패러다임으로 여겨짐
    • API 서버에서 엄격하게 정의된 endpoint 들에 요청하지 않고, 한 번의 요청으로 정확히 가져오고 싶은 데이터 가져오는 쿼리 제공
  • Rest API vs GraphQL
    • REST
      • Resource에 대한 형태 정의와 데이터 요청 방법이 연결되어 있음
      • Resource의 크기와 형태를 서버에서 결정
      • URI가 Resource 나타내고 Method가 작업의 유형 표현
      • 여러 Resource에 접근하고자 할 때 여러번의 요청이 필요
      • 각 요청의 엔드포인트에 정의된 핸들링 함수 호출하여 작업 처리
      • 다양한 기종에서 필요한 정보들을 일일히 구현하는 것이 힘들었음
    • GraphQL
      • Resource에 대한 형태 정의와 데이터 요청이 완전히 분리됨
      • Resource에 대한 정보만 정의하고, 필요한 크기와 형태는 client 단에서 요청 시 결정
      • Schema가 Resource 나타내고 Query, Mutation 타입이 작업의 유형 나타냄
      • 한번의 요청에서 여러 Resource에 접근 가능
      • 요청 받은 필드에 대한 resolver를 호출하여 작업 처리
  • Rest API => GraphQL

    • RestAPI
      {
          "title": "Romance of the Three Kingdoms",
          "author": {
              "firstName": "Luo",
              "lastName": "Guanzhong"
          }
      }
      
    • GraphQL

      • 우선 타입 정의
        • 리소스의 유형과 리소스를 가져오는 방식이 분리되어 있음
        • Client에서 데이터를 어떻게 요청할 수 있는지 정보 X
          type Book {
              id: ID
              title: String
              author: Author
          }
          type Author {
              id: ID
              firstName: String
              lastName: String
              books: [Book]
          }
          
      • Book & Author에 접근하도록 Query 타입 필요
        type Query {
            book(id: ID!): Book
            author(id: ID!): Author
        }
        
      • 이후 쿼리는 다음과 같은 방식
        • POST: /graphql?query={book(id:"1") {title, author {firstName}}}
        • Rest와 다르게 /books 와 같이 Resource에 대한 엔드포인트 존재 x
  • GraphQL의 장점
    • Frontend 단에서 딱 필요한 정보만 별도 요청 가능
    • 클라이언트/서버 간 오류 줄여줌
    • GraphQL 기존 쿼리 중단하지 않고도 진화할 수 있도록 허용
    • HTTP 요청의 횟수를 줄일 수 있다
    • HTTP 응답의 Size를 줄일 수 있다

Connectable에서 GraphQL 필요한가?

  • 하나의 백엔드 서버를 두고, 아티스트 대시보드/팬 앱을 운영할 것
  • 아티스트 도메인 경우 각각의 앱에서 줘야할 정보가 다름
  • Authentication을 거쳐서 아티스트 대시보드 측이 요청한 graphQL에 알맞게 응답을 줄 수 있다면 유의미할 듯?

Spring에서 GraphQL

  • GraphQL Schemas
    type Post {
        id: ID!
        title: String!
        text: String!
        category: String
        author: Author!
    }
    
    type Author {
        id: ID!
        name: String!
        posts: [Post]!
    }
    
    type Query {
        recentPosts(count: Int, offset: Int): [Post]!
    }
    
    type Mutation {
        writePost(title: String!, text: String!, category: String) : Post!
    }
    
  • SpringBoot에 적용하기

    1. 의존성 주입하기

      • graphql-spring-boot-starter
        dependencies {
            // GraphQL
            implementation 'com.graphql-java:graphql-spring-boot-starter:5.0.2'
            implementation 'com.graphql-java:graphql-java-tools:5.2.4'
        }
        
    2. 스키마 파일 작성하기

      • .graphqls 라는 확장자로 저장
    3. 루트 쿼리 처리를 위한 bean 설정 (GraphQLQueryResolver 구현)

      • query 타입의 메서드를 GraphQLQueryResolver 인터페이스로 구현
      • 스키마에 있는 필드가 클래스의 메소드로써 모두 존재해야 함
        @Component
        public class MyQuery implements GraphQLQueryResolver {
            private final PostRepository postRepository;
        
            public List<PostResponse> getRecentPosts(int count, int offset) {
                final List<Post> all = postRepository.findAll();
                return PostResponse.from(all);
            }
        }
        
    4. GraphQL 타입 나타내기

      public class Post {
          private String id;
          private String title;
          private String category;
          private String authorId;
      }
      
      public class PostResponse {
          private long id;
          private String title;
          private String text;
          private String category;
          private Author author;
      }
      

    5. Mutation

      • GraphQL은 서버에 저장된 데이터를 업데이트 하기 위한 행위
        public class MyMutation implements GraphQLMutationResolver {
            private final PostRepository postRepository;
            private final AuthorRepository authorRepository;
        
            public PostResponse writePost(String title, String text, String category) {
                Post post = new Post();
                post.setTitle(title);
                post.setCategory(category);
                post.setAuthor(authorRepository.getOne(1L));
                postRepository.save(post);
                return PostResponse.from(post);
            }
        }
        

NFT 컬렉션 발행해보기