RestTemplate

RestTemplate은 HTTP 클라이언트 라이브러리를 통해 높은 수준의 API를 제공한다. REST 엔드포인트를 코드 한줄로 호출하기 쉽게 해준다. 오버로드된 메소드들은 다음과 같다.

 

RestTemplate methods

Method group Description
getForObject GET method를 통해 representation을 조회
getForEntity GET method를 통해 ResponseEntity를 조회 (status, headers, body)
headForHeaders HEAD method를 통해 리소스의 모든 헤더들을 조회
postForLocation POST method를 통해 새로운 리소스를 생성하고 응답에 Location 헤더를 리턴
postForObject POST method를 통해 새로운 리소스를 생성하고 응답에 representation을 리턴
postForEntity POST method를 통해 새로운 리소스를 생성하고 응답에 representation을 리턴
put PUT method를 통해 리소스를 새로 생성하거나 수정
patchForObject PATCH method를 통해 리소스를 수정하고 응답의 representation을 리턴.
주의할 것은 JDK HttpURLConnection은 PATCH 를 지원하지 않고, Apache HttpComponents와 다른 것들은 지원함
delete DELETE method를 통해 특정 URI의 리소스를 삭제
optionsForAllow ALLOW method를 통해 리소스가 허용하는 HTTP method들을 조회
exchange 위의 method들 보다 더 일반화되고, 덜 선택적인 버전으로 필요한 경우 추가적인 유연성을 제공함
HTTP 메서드, URL, 헤더 및 본문을 포함한 RequestEntity를 입력받아 ResponseEntity를 리턴
이러한 방법을 사용하면 Class 대신 ParameterizedTypeReference를 사용하여 제네릭 타입 응답을 지정할 수 있음
execute 요청을 수행하는 가장 일반화된 방법으로, 콜백 인터페이스를 통한 요청 준비 및 응답 추출에 대한 완벽한 제어가 가능

 

초기화

기본 생성자는 java.net.HttpURLConnection 라이브러리를 사용한다. ClientHttpRequestFactory를 사용하여 다른 HTTP 라이브러리로 바꿀 수 있다. 스프링은 Apache HttpComponents, Netty, OkHttp를 지원한다.

  • 예)
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

ClientHttpRequestFactory는 기본 HTTP 클라이언트 라이브러리(예: 자격 증명, 연결 풀링 등)에 대한 구성 옵션을 제공한다. 주의할 점은 java.net 패키지의 HTTP 구현체를 사용할 경우 401 상태를 가진 응답 객체에 접근할 때 익셉션이 발생할 수 있으므로, 이럴 경우 다른 HTTP 라이브러리 사용해야 한다.

 

URIs

대부분의 RestTemplate 메소드는 String vararg 또는 Map<String, String>을 통해 URI 템블릿과 URI 템플릿 변수를 허용한다.

/* String vararg */
String result = restTemplate.getForObject(
        "http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");

/* Map<String, String> */
Map<String, String> vars = Collections.singletonMap("hotel", "42");

String result = restTemplate.getForObject(
        "http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);

URI 템플릿은 자동으로 인코딩된다.

restTemplate.getForObject("http://example.com/hotel list", String.class);

// 요청 url: "http://example.com/hotel%20list"

RestTemplateuriTemplateHandler 속성을 사용하여 URI 인코딩 방법을 지정할 수 있다 또는 java.net.URI를 사용하여 이를 사용하는 RestTemplate 메소드에 인자로 전달할 수 있다.

 

Headers

exhcnage() 메소드를 사용하여 요청 헤더를 지정할 수 있다.

String uriTemplate = "http://example.com/hotels/{hotel}";
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(42);

RequestEntity<Void> requestEntity = RequestEntity.get(uri)
        .header(("MyRequestHeader", "MyValue")
        .build();

ResponseEntity<String> response = template.exchange(requestEntity, String.class);

String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();

ResponseEntity를 반환하는 RestTemplate 의 여러 메소드를 통해 응답 헤더를 얻을 수 있다.

 

Body

RestTemplate 메서드에 전달되고 반환되는 객체는 HttpMessageConverter의 도움으로 raw content로 변환된다.

POST 메서드에서 입력된 객체는 요청 body에 직렬화된다.

URI location = template.postForLocation("http://example.com/people", person);

요청의 Content-Type 헤더를 명시적으로 설정할 필요는 없다. 대부분의 경우 소스 객체 타입에 따라 호환되는 메시지 컨버터를 찾을 수 있고, 선택한 메시지 컨버터가 적절히 content-type을 설정한다. 필요한 경우 exchange 메서드를 사용하여 Content-Type 요청 헤더를 명시적으로 제공할 수 있으며, 이는 선택된 메시지 컨버터에 영향을 미칠 수 있다.

GET 메서드에서는 응답의 body가 출력 객체로 역직렬화된다.

Person person = restTemplate.getForObject("http://example.com/people/{id}", Person.class, 42);

요청의 Accept 헤더는 명시적으로 설정할 필요가 없다. 대부분의 경우 Accept 헤더를 예상 응답 유형에 따라 호환되는 메시지 컨버터를 찾을 수 있다. 이 컨버터는 Accept 헤더를 채우는데 도움이 된다. 필요한 경우 exchange 메서드를 사용하여 Accept헤더를 명시적으로 제공할 수 있다.

기본적으로 RestTemplate은 선택적인 변환 라이브러리가 있는지 확인하는데 도움이 되는 클래스패쓰 검사에 따라 모든 내장 메시지 컨버터들을 등록한다. 또, 명시적으로 메시지 컨버터를 설정할 수 있다.

 

Message Conversion

spring-web 모듈은 InputStreamOutputStream을 통해 HTTP 요청 및 응답의 body를 읽고 쓰는 역할을 맡은 HttpMessageConverter를 포함한다. HttpMessageConverter는 클라이언트 측(예, RestTemplate)과 서버 측 (예, Spring MVC REST controllers)에서 사용된다.

주요 미디어(MIME) 유형에 대한 구체적인 구현은 프레임워크에서 제공되며 기본적으로 클라이언트 측 RestTemplate 및 서버측 RequestMethodHandlerAdapter에 등록된다. (메시지 구성 참조)

HttpMessageConverter의 구현은 다음 섹션에 기술되어있다. 모든 컨버터들이 기본 미디어 타입을 사용하지만 supportedMediaTypes 빈 프로퍼티 설정으로 오버라이드 할 수 있다.

 

HttpMessageConverter 구현체 목록

MessageConverter Description
StringHttpMessageConverter HTTP 요청 및 응답으로부터 문자열(String)을 읽고 쓸 수 있는 구현체
기본적으로 모든 텍스트 미디어 타입들 (text/*)을 지원하며, text/plain Content-Type으로 작성한다.
FormHttpMessageConverter HTTP 요청 및 응답으로부터 form 데이터를 읽고 쓸 수 있는 구현체
기본적으로 application/x-www-form-urlencoded 미디어 타입을 지원한다.
Form 데이터는 MultiValueMap<String, String>읽고 작성된다.
ByteArrayHttpMessageConverter HTTP 요청 및 응답으로부터 바이트 배열을 읽고 쓸 수 있는 구현체
기본적으로 모든 미디어 타입 (*/*)을 지원하며, application/octet-stream Content-Type으로 작성한다.
이 컨버터는 supportedMediaTypes 속성을 설정하고 getContentType(byte[])를 오버라이드하여 재정의할 수 있다
MarshallingHttpMessageConverter org.springframework.oxm 패키지의 스프링의 마샬러(Marshaller)와 언마샬러(Unmarshaller) 추상화를 사용하여 XML을 읽고 쓸 수 있는 구현체
이 컨버터는 사용하기 전에 마샬러와 언마샬러를 필요로 한다.
이들은 생성자 또는 빈 프로퍼티를 통해 주입될 수 있다.
기본적으로 text/xmlapplication/xml을 지원한다
MappingJackson2HttpMessageConverter Jackson 라이브러리의 ObjectMapper를 사용하여 JSON을 읽고 쓸 수 있는 구현체
JSON 매핑은 필요에 따라 Jackson이 제공하는 어노테이션을 통해 커스터마이징 될 수 있다
추가 제어가 필요할 경우 특히, 특정 유형에 대해 커스텀 JSON 직렬화/역직렬화를 제공해야 하는 경우 ObjectMapper 속성을 통해 커스텀 ObjectMapper를 주입할 수 있다.
기본적으로 application/json을 지원한다
MappingJackson2XmlHttpMessageConverter Jackson XML의 XmlMapper를 사용하여 XML을 읽고 쓸 수 있는 구현체
Jackson이 제공하는 어노테이션을 또는 JAXB를 사용하여 필요에 따라 XML 매핑은 커스터마이징 될 수 있다
추가 제어가 필요한 경우, 특히 특정 유형에 대해 커스텀 XML 직렬화/역직렬화를 제공해야하는 경우 ObjectMapper 속성을 통해 커스텀 XmlMapper를 주입할 수 있다
기본적으로 application/xml을 지원한다
SourceHttpMessageConverter HTTP 요청 및 응답으로부터 javax.xml.transform.Source를 읽고 쓸 수 있는 구현체
DOMSource, SAXSource, StreamSource만 지원된다.
기본적으로 text/xmlapplication/xml를 지원한다
BufferedImageHttpMessageConverter HTTP 요청 및 응답으로부터 java.awt.image.BufferedImage를 읽고 쓸 수 있는 구현체
Java I/O API에서 지원하는 미디어 타입을 읽고 쓴다

 

Jackson JSON Views

객체 속성의 일부만 직렬화하도록 Jackson JSON View를 지정할 수 있다.

  • 예)
MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);

RequestEntity<MappingJacksonValue> requestEntity =
    RequestEntity.post(new URI("http://example.com/user")).body(value);

ResponseEntity<String> response = template.exchange(requestEntity, String.class);

 

Multipart

multipart 데이터를 전송하기 위해 multipart 내용을 대표하는 Objects 또는 내용과 헤더를 대표하는 HttpEntity를 값으로 가지는 MultiValueMap<String, ?>를 제공할 필요가 있다. MultipartBodyBuilder는 multipart 요청을 만들어주는 편리한 API를 제공한다.

MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));

MultiValueMap<String, HttpEntity<?>> parts = builder.build();

대부분의 경우 각 part에 Content-Type를 지정할 필요가 없다. Content-Type은 직렬화 하도록 선택한 HttpMessageConverter 또는 파일 확장자에 기반한 Resource의 경우에 자동으로 결정된다. 필요한 경우 오버로드된 builder 메소드를 통해 각 part에 사용할 MediaType을 명시적으로 제공할 수 있다.

MultiValueMap이 준비되면, RestTemplate에 인자를 넘겨줄 수 있다.

MultipartBodyBuilder builder = ...;
template.postForObject("http://example.com/upload", builder.build(), Void.class);

MultiValueMap이 일반적인 form 데이터를 나타낼수 있는(예: application/x-www-form-urlencoded) String이 아닌 값을 가질 경우, Content-Typemultipart/form-data로 지정할 필요 없다. 이는 ㅇ항상 HttpEntity 래퍼를 보장하는 MultipartBodyBuilder를 사용하는 경우에 해당된다.

 

참고

 

스프링 프레임워크 정적 자원 버전 관리




HTTP 캐싱

  • 네트워크를 통해 자원을 가져오는 작업은 느리고 비용이 발생한다. 특히, 크기가 크면 그만큼 브라우저가 화면을 표시하는데 오래 걸려 사용자 경험이 나빠진다.
  • 가져온 자원을 캐시했다가 재활용하는 것은 성능 최적화에 중요하다.
  • 모든 브라우저는 HTTP 캐시 구현 기능이 포함되어 있다.
  • 개발자는 각 서버 응답이 브라우저에 응답을 캐시할 수 있는 시점과 그 기간을 지시하게 위한 HTTP Header를 올바르게 제공해야한다.

정적 자원 버전 관리

  • 브라우저는 한 번 방문한 사이트의 정적 자원은 캐시가 되어 만료 될 때까지 사용한다.
  • 이때문에 정적 자원이 수정된 후에 사이트를 방문한 사용자는 최신 정적 자원을 사용하는 반면, 이전에 방문했던 사용자는 과거 정적 자원을 사용하게 되어 이슈가 발생할 수 있다.
  • 예) 이미지가 다르게 보임, 수정된 자바스크립트가 실행이 안되거나 동작이 달라짐
  • 따라서 정적 자원의 버전관리를 통해 수정된 정적 자원일 경우 새로운 버전이 제공되어 사용자들이 서버에서 최신 버전 정적 자원을 받을 수 있도록 해야한다.

정적 자원 캐시 만료되는 경우

  • 사용자 직접 삭제
  • max-age 또는 expires로 정해진 기한이 지난 경우
  • 정적 자원의 URL을 변경한 경우

스프링 프레임워크에서 정적 자원 버전 관리

  • 스프링 프레임워크는 4.1 버전 이후부터 WebMvcConfigureraddResourceHandlers 설정 메서드에서 버전 관리 핸들러를 리졸버를 등록하여 관리할 수 있다.
  • ContentVersionStrategy 통해서 브라우저가 요청한 자원 각 파일의 내용에 따라 MD5 해쉬된 문자열이 요청한 자원 파일명 뒤에 버전처럼 붙는다.
  • 수정된 정적 자원의 경우 파일 내용이 바뀌므로 서버에서 응답한 요청한 자원 파일명에 붙은 MD5 해쉬 문자열이 사용자의 브라우저가 캐시해서 가지고 있던 것과 다르기 때문에 새로 내려받게 된다.

WebMvcConfig 설정

@Configuration
public class MvcConfig implements WebMvcConfigurer {

@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**", "/webjars/**")
                .addResourceLocations("/resources/", "classpath:/META-INF/resources/webjars/")
                .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
                .resourceChain(true)
                .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
                .addResolver(new WebJarsResourceResolver());
	}

}

정적 자원 접근 처리 설정

  • 컨트롤러 어드바이스 설정을 ResourceUrlProvider를 모든 응답에 urls라는 이름으로 제공한다.
  • 템플릿에서 urls로 접근이 가능하다.
@ControllerAdvice
public class ResourceUrlAdvice {

    @Autowired
    private ResourceUrlProvider resourceUrlProvider;

    @ModelAttribute("urls")
    public ResourceUrlProvider urls() {
        return this.resourceUrlProvider;
    }
}

JSP에서 사용

<!-- CSS -->
<link rel="stylesheet" href="${urls.getForLookupPath('/resources/sss/style.css')}" />

<!-- JavsScript -->
<script src="${urls.getForLookupPath('/resources/js/common/common.js')}"></script>
<script src="${urls.getForLookupPath('/resources/js/main.js')}"></script>

예시

이전 버전


변경된 버전


흐름



생각해볼 점

  • 하나의 HTTP 요청에 필요한 정적 자원을 하나씩 검사하는 부분이 추가 되므로 극악한 상황의 네트워크, 서버 환경에서는 성능에 악영향을 줄 수도 있을 것 같다.
  • 하지만 일반적으로 문제는 없고, 오히려 버전 관리가 안되서 사용자가 과거 버전의 자원을 가진 채로 사이트가 운영됨으로 발생하는 이슈에 대한 비용이 더 클 것 같다.

참고


매직 넘버 치환

  • 소스 코드에 특정한 숫자(매직 넘버(magic number))를 직접 작성하는 것은 나쁜 코딩 스타일

나쁜 예

public class MagicNumber {
    public static void main(String[] args) {
        MagicNumber magicNumber = new MagicNumber();
        MagicNumber.Player player = magicNumber.new Player("Hoonmaro");
        // 0은 공격
        player.action(0);
        // 1은 방어
        player.action(1);
    }

    public class Player {

        private String name;

        public Player() {
        }

        public Player(String name) {
            this.name = name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public void action(int command) {
            if (command == 0) {
                attack();
            } else if (command == 1) {
                defence();
            }
        }

        private void attack() {
            System.out.println(this.name + " is going to Attack!!");
        }

        private void defence() {
            System.out.println(this.name + " is going to Defence!!");
        }

    }
}

나쁜이유

  1. 매직 넘버의 의미를 알기 어렵다
    • 0과 1이 무슨 의미인가?
    • COMMAND_ATTACK과 같은 기호 상수를 쓰면 의미를 알기 쉽다
  2. 매직 넘버는 수정하기 어렵다
    • 0 또는 1 커맨드 숫자가 변경 될 경우 치환해야 하는데, 만약 많은 곳에서 동일한 커맨드를 쓴다면?
    • 손이 많이 가고, 틀리기 쉽고, 빼먹는 경우가 생길 수도 있다.

카탈로그

이름 매직 넘버를 기호 상수로 치환 (Replace Magic Number with Symbolic Constant)
상황 상수를 사용하고 있음
문제 • 매직 넘버는 알기 어려움
• 매직 넘버가 여러 곳에 있으면 변경하기 어려움
해법 매직 넘버를 기호 상수로 치환함
결과 장점
• 상수의 의미를 알기 쉬워짐
• 기호 상수의 값을 변경하면 상수를 사용하는 모든 곳이 변경됨
단점/주의 • 이해하기 어려운 이름을 사용하면 오해가 생길 수 있음
방법
  1. 기호 상수 선언하기
    • 1. 기호 상수 선언
    • 2. 매직 넘버를 기호 상수로 치환
    • 3. 기호 상수에 의존하는 다른 매직 넘버를 찾아서 기호 상수를 사용한 표현식으로 변환
    • 4. 컴파일
  2. 2. 테스트
    • 1. 모든 기호 상수 치환이 끝나면 컴파일해서 테스트
    • 2. 가능하다면 기호 상숫값을 변경한 후 컴파일해서 테스트 |
관련 항목 • 분류 코드를 클래스로 치환
• 분류 코드를 상태/전략 패턴으로 치환

리팩토링

기호 상수 선언하기

  1. 기호 상수 선언
  • public static final 클래스 필드 사용
  • 또는 enum 사용
  • 어떤 클래스 안에서만 사용할 기호 상수를 선언할 경우 private 접근지시자를 사용할 수 있다
public static final int COMMAND_ATTACK = 0;
public static final int COMMAND_DEFENCE = 1;
  1. 매직 넘버를 기호 상수로 치환
  • 0, 1과 같은 매직 넘버를 기호 상수로 치환
// if (command == 0) {
if (command == COMMAND_ATTACK) {

// else if (command == 1) {
} else if (command == COMMAND_DEFENCE) {


// player.action(0)
// player.aciton(1)
player.action(Player.COMMAND_ATTACK);
player.action(Player.COMMAND_DEFENCE);
  1. 기호 상수에 의존하는 다른 매직 넘버를 기호 상수를 사용한 표현식으로 변환
  • 상수 의존 관계는 상수 사이에 의존 관계가 있어 한 상수의 변경이 다른 상수에게도 영향을 미치는 경우를 말한다.
  • 이 때, 표현식으로 의존 관계를 표현해야 한다.
// public static final int MAX_INPUT_LENGTH = 100;
// public static final int WOR_ARE_LENGTH = 100 * 2;

public static final int MAX_INPUT_LENGTH = 100;
public static final int WOR_ARE_LENGTH = MAX_INPUT_LENGTH * 2;

테스트

  1. 모든 기호 상수 치환이 끝나면 컴파일해서 테스트

    • 테스트 결과는 리팩토링하기 전과 같아야 한다.
  2. 가능하다면 기호 상숫값을 변경한 후 컴팡리해서 테스트

    • 기호 상수의 값을 다른 값으로 변경한 후 테스트하면 빠드린 곳이 없는지 확인할 수 있다.
    • COMMAND_ATTACK 값을 0에서 1000으로 변경해도, 매직 넘버가 없다면 테스트가 성공하지만 매직 넘버가 있다면 테스트가 실패한다.

리팩토링 후

public class MagicNumber {
    public static void main(String[] args) {
        MagicNumber magicNumber = new MagicNumber();
        MagicNumber.Player player = magicNumber.new Player("Hoonmaro");
        player.action(Player.COMMAND_ATTACK);
        player.action(Player.COMMAND_DEFENCE);
    }

    public class Player {

        public static final int COMMAND_ATTACK = 0;
        public static final int COMMAND_DEFENCE = 1;


        private String name;

        public Player() {
        }

        public Player(String name) {
            this.name = name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public void action(int command) {
            if (command == COMMAND_ATTACK) {
                attack();
            } else if (command == COMMAND_DEFENCE) {
                defence();
            }
        }

        private void attack() {
            System.out.println(this.name + " is going to Attack!!");
        }

        private void defence() {
            System.out.println(this.name + " is going to Defence!!");
        }

    }
}
  • 기호 상수가 충분한 정보를 제공하므로 주석이 필요 없다.

더 나은 리팩토링

  • 기호 상수로 만든다고 해도 실제로는 상수 값이므로 매직 넘버를 직접 적어도 문제없이 컴파일 된다.
  • 실수가 생길 수 있다.
  • 커맨드 클래스 객체를 활용하거나 enum을 활용한다.

enum 활용

  • 자바 5부터 enum을 사용할 수 있다.
public class EnumMagicNumber {
    public static void main(String[] args) {
        EnumMagicNumber magicNumber = new EnumMagicNumber();
        Player player = new Player("Hoonmaro");
        player.action(Player.Command.ATTACK);
        player.action(Player.Command.DEFENCE);
    }
}

// Player 클래스 분리
public class Player {

    public enum Command {
        ATTACK,
        DEFENCE
    }

    private String name;

    public Player() {
    }

    public Player(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void action(Command command) {
        if (command == Command.ATTACK) {
            attack();
        } else if (command == Command.DEFENCE) {
            defence();
        }
    }

    private void attack() {
        System.out.println(this.name + " is going to Attack!!");
    }

    private void defence() {
        System.out.println(this.name + " is going to Defence!!");
    }

}

  • 기존 Player Inner 클래스를 분리하였다.
  • 증첩 enum 은 암묵적으로 static 클래스 이기 때문에 Inner 클래스 안에 선언할 수 없기 때문이다.
  • Java Language Spec 8.9 Enums 참고
  • enum을 활용하여 직관적으로 의미를 전달 할 수 있다.
  • 상수가 아니므로 잘못 사용된 곳에서 컴파일에서 경고가 발생하여 에러를 사전에 방지할 수 있다.

기호 상수가 적합하지 않은 경우

  • 배열 길이
    • 배열 길이는 배열 객체에 length라는 필드가 있다.
    • 기호 상수가 언제나 올바른 배열길이가 아닐 수도 있다.
  • 잘 알려진 값을 대체하는 기호 상수 (오히려 가독성이 떨어짐)

바이트 코드에 내장된 상수에 주의

  • 필드값이 컴파일할 때 정해지는 상수일 경우 리컴파일시 변경된 값으로 바뀌겠지만,
  • 이를 사용하는 다른 클래스에서는 리컴파일 하기 전에 이전 상수 값을 가지고 있으므로 문제가 발생한다.
  • 매직 넘버를 치환한 모든 소스코드를 리컴파일 해야 정상 작동 된다.

AJAX Patterns

  • Vue에서 AJAX를 사용하는 가장 좋은 방법에 대해서는 개발자들 사이에서도 의견이 갈린다.
  • 그 중 4가지의 디자인 패턴을 소개하며, 각각의 장단점을 알고 상황에 맞게 쓸 수 있어야 한다.
  • AJAX Design Patterns in Vue.js
    1. Root instance
    2. Components
    3. Vuex ations
    4. Route navigation guards

1. Root instance

  • 이 아키텍쳐에서는 root instance로부터 모든 AJAX 요청이 보내지고 모든 상태가 저장된다.

  • 다른 하위 컴포넌트들이 data를 필요로 한다면, props를 통해 전달한다.

  • 하위 컴포넌트들이 data 최신화를 원하면, 커스텀 이벤트를 통해 root instacne의 AJAX 요청을 호출한다.

  • 예제

new Vue({
  data: {
    message: ''
  },
  methods: {
    refreshMessage(resource) {
      this.$http.get('/message').then((response) {
        this.message = response.data.message;
      });
    }
  }
})

Vue.component('sub-component', {
  template: '<div>{{ message }}</div>',
  props: [ 'message' ]
  methods: {
    refreshMessage() {
      this.$emit('refreshMessage');
    }
  }
});

장점

  • 모든 AJAX 로직과 data를 한 곳에서 관리할 수 있다.
  • 모든 컴포넌트들이 프레젠테이션에 집중할 수 있다.

단점

  • 많은 props와 커스텀 이벤트가 필요하면서 앱의 크기가 커진다.

2. Components

  • 이 아키텍쳐에서는 컴포넌트 별로 AJAX 요청과 상태를 독립적으로 관리한다.

  • 일반적으로 컨테이너 역할을 하는 컴포넌트가 data를 관리하고 하위 컴포넌트들을 프레젠테이션에 집중시킨다.

  • 예제

let mixin = {
  methods: {
    callAJAX(resource) {
      ...
    }
  }
}

Vue.component('container-comp', {
  // No meaningful template, I just manage data for my children
  template: '<div><presentation-comp :mydata="mydata"></presentation-comp></div>', 
  mixins: [ myMixin ],
  data() {
    return { ... }
  },

})

Vue.component('presentation-comp', {
  template: <div>I just show stuff like {{ mydata }}</div>,
  props: [ 'mydata' ]
})

장점

  • 컴포넌트 결합도를 낮추고 재활용성을 높인다.
  • data를 필요할 때 얻을 수 있다.

단점

  • 다른 컴포넌트들과 data를 주고받기 쉽지 않다.
  • 컴포넌트가 너무 많은 책임을 가지게 되거나 기능적 중복이 발생할 수 있다.

3. Vuex actions

  • 이 아키텍쳐에서는 AJAX 로직과 상태를 Vuex Store에서 관리할 수 있다.

  • 컴포넌트들은 action을 호출하여 새로운 data를 요청할 수 있다.

  • 이 패턴을 구현하는 경우, AJAX요청(예, 로딩 스피너 숨기기, 버튼 재활성화)의 해결에 반응할 수 있도록 액션에서 promise를 반환하는 것이 좋다.

  • 예제

store = new Vuex.Store({
  state: {
    message: ''
  },
  mutations: {
    updateMessage(state, payload) {
      state.message = payload
    }
  },
  actions: {
    refreshMessage(context) {
      return new Promise((resolve) => {
        this.$http.get('...').then((response) => {
          context.commit('updateMessage', response.data.message);
          resolve();
        });
      });
    }
  }
});

Vue.component('my-component', {
  template: '<div>{{ message }}</div>',
  methods: {
    refreshMessage() {
      this.$store.dispatch('refeshMessage').then(() => {
        // do stuff
      });
    }
  },
  computed: {
    message: { return this.$store.state.message; }
  }
});

장점

  • 추가적인 props와 커스텀 이벤트 없이도 Root instance 패턴과 Components 패턴의 장점을 모두 가진다.

단점

  • Vuex를 사용하기 위한 학습비용 등의 오버헤드

4. Route navigation guards

  • 애플리케이션이 여러 페이지로 분할되고 route가 변경되면 해당 페이지와 하위 컴포넌트들에 필요한 모든 데이터를 가져온다.

  • 이 방식의 주요 이점은 UI가 굉장히 단순해지는 것이다.

  • 만약 컴포넌트들이 독립적으로 data를 가져오면, 컴포넌트 데이터가 임의의 순서로 입력되어 페이지가 예측 불가능하게 재전송된다.

  • 예제

import axios from 'axios';

router.beforeRouteEnter((to, from, next) => {
  axios.get(`/api${to.path}`).then(({ data }) => {
    next(vm => Object.assign(vm.$data, data))
  });
})

장점

  • UI를 보다 쉽게 예측할 수 있다.

단점

  • data가 준비될 때가지 페이지가 렌더링 되지 않으므로 전체적으로 느려진다.
  • routes를 사용하지 않는다면 도움이 되지 않는다.

참고


객체지향 프로그래밍

  • Object Oriented Programming, OOP *객체
    • 생활에서 일종의 물건
    • 속성(Attribute)와 행동(Action)을 가짐
  • OOP는 속성은 변수, 행동은 함수로 객체의 개념을 프로그램으로 표현

클래스, 객체, 인스턴스

  • 클래스는 객체의 구체적인 형태를 설계한 명령어의 집합, 템플릿 또는 설계도
  • 객체는 속성과 행위로 구성된 사물 또는 추상적 개념들의 정보를 표현한 것
  • 인스턴스는 클래스로부터 인스턴스화를 통해 구현된 객체

추상화 기법

  • 추상화 기법의 하나로 분류/인스턴스화(classification/instantiation) 개념 존재
  • 분류는 객체들을 공통적인 속성을 공유하는 개념으로 범주화
  • 인스턴스화는 추상화된 범주로부터 실재하는 객체를 만드는 과정을 의미
  • 인스턴스라는 말은 추상적인 개념과 구체적인 객체 사이의 관계에 초점에 맞춘 용어

프로그램에서의 클래스, 인스턴스

  • 추상화의 과정을 거쳐 속성과 행위를 가진 객체를 클래스로 표현하며, 클래스는 변수와 메서드를 가진다
  • 프로그램 언어별로 클래스를 만드는 문법을 사용하여 인스턴스를 생성하게 된다.
    • Java: ExampleClass exampleClass = new ExampleClass();
    • Python: example_class = ExampleClass()

문법

[접근지정자] [기타제어자] class 클래스명 [extends Super클래스] [implements 인터페이스…] {
	/* 필드(멤버변수) */
	/* 메서드(멤버함수) */
}
  • 멤버: 클래스 안에 선언된 것들

클래스명 작성 규칙

작성규칙
하나 이상의 문자로 이루어져야 한다 Car, SportsCar
첫 번째 글자는 숫자가 올 수 없다 Car, 3Car(x)
'$','_' 외의 특수 문자는 사용할 수 없다 $car, _Car, @Car(x), #Car(x)
자바 키워드(예약어)는 사용할 수 없다 int(x), for(x)
단일 단어 => 첫문자는 대문자 혼합 단어 => 각 단어 첫글자는 대문자

예제

  • Cat 클래스
public class Cat {
	
	/* 클래스 필드 (객체의 속성) */
	// 이름
	private String name;
	// 품종
	private String breed;
	// 나이
	private int age;
	
	/* 클래스 메서드 (객체의 행동) */
	public String cry() {
		return "냐옹";
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public void setBreed(String breed) {
		this.breed = breed;
	}
	
	public void setAge(int age) {
		this.age = age;
	}
	
	public String getName() {
		return this.name;
	}
	
	public String getBreed() {
		return this.breed;
	}
	
	public int getAge() {
		return this.age;
	}
	
	// 모든 클래스의 부모인 Object 클래스의 toString 메서드 오버라이드
	@Override
	public String toString() {
		final StringBuilder sb = new StringBuilder("Cat{");
			sb.append("name='").append(name).append('\'');
			sb.append(", breed='").append(breed).append('\'');
			sb.append(", age='").append(age).append('\'');
			sb.append('}');
		return sb.toString();
	}
}
  • Cat 클래스 인스턴스화
public class CatClassTest {
	public static void main(String[] args) {
		// new 연산자를 통해 Cat 클래스를 인스턴스화
		Cat nabi = new Cat();
		nabi.setName("나비");
		nabi.setBreed("페르시안");
		nabi.setAge(2);
		System.out.println(nabi);
	}
}
  • 결과
Cat{name='나비', breed='페르시안', age='2'}

예제 설명

  • Cat 클래스를 만들고 클래스의 필드와 메서드를 선언
  • CatClassTest 클래스에서 Cat 클래스를 new 연산자를 통해 인스턴스화하여 인스턴스를 생성 후 nabi라는 변수에 레퍼런스 저장
  • 이 때 new 연산자를 통해 생성된 인스턴스는 힙(heap) 메모리 영역에 저장되며 nabi 변수는 메모리 레퍼런스(주소)를 가지고 있다.
  • setter(세터) 메서드를 통해 name, breed, age를 세팅 후 오버라이드한 toString 메서드를 통해 필드값들을 출력한다.

구성 멤버

필드(field)

  • 객체의 고유 데이터, 상태 정보를 저장하는 곳
  • 생성자와 메소드 전체에서 사용되며 객체가 소멸되지 않는 한 객체와 함께 존재
  • 초기화 하지 않으면 각 자료형의 기본값으로 초기화 됨
  • 필드선언
    • 생성자 선언과 메소드 선언의 앞 뒤 어떤 곳에서도 필드 선언 가능
    • 단, 생성자와 메소드 내부에서는 선언될 수 없음 (생성자,메소드 내부의 변수는 지역변수)
  • 필드사용
    • 필드 값을 읽고 변경하는 작업
    • 클래스 내부에서는 단순히 필드 이름으로 읽고 변경
    • 클래스 외부에서는 클래스로부터 객체 생성 후 사용 (일반적으로 직접 접근 못하게 캡슐화)

생성자(Constructor)

  • new 연산자로 호출되는 특별한 중괄호 블록
  • 객체 생성 시 초기화 담당
  • 필드를 초기화하거나 메소드를 호출해서 객체를 사용할 준비
  • 클래스 이름으로 되어 있고 리턴 타입이 없다
  • 생성자 선언
    • 생성자를 선언 안해도 기본적으로 컴파일시 디폴트 생성자가 생성됨
    • 생성자는 메소드와 비슷한 모양을 가지나, 리턴 타잆이 없고 클래스 이름과 동일
    • 클래스에 생성자가 명시적으로 선언되어 있을 경우 반드시 선언된 생성자를 호출해서 객체를 생성
    • 하나라도 인자값을 가진 다른 생성자를 호출할 경우 디폴트 생성자는 자동으로 생성되지 않으므로 디폴트 생성자를 호출하기 위해선 디폴트 생성자도 명시적으로 선언해줘야 함
  • 필드 초기화
    • 필드를 선언할 때 초기값을 주면 동일한 클래스로부터 생성되는 객체들은 모두 같은 데이터를 가짐
    • 객체 생성 시점에 외부에서 제공되는 다양한 값들로 초기화 되어야 한다면 생성자에서 초기화 해야 함
  • 관례적으로 필드와 동일한 이름을 갖는 매개변수 사용
    • 이 경우 필드와 매개변수 이름이 동일하므로 생성자 내부에서 해당 필드에 접근할 수 없다
    • why? 동일한 이름의 매개 변수가 사용 우선순위가 높다. 따라서 this 를 사용한다.
    • this는 객체 자신의 참조
public class Cat {
	private String name;
	public setName(String name) {
		// 필드 name과 매개변수 name의 이름이 같다
		// name = name
		this.name = name
	}
	
}
  • 생성자 오버로딩
    • 매개변수를 달리하는 생성자를 여러 개 선언하는 것
    • 오버로딩 시 주의점은 매개 변수의 타입과 개수 그리고 선언된 순서가 똑같을 경우 매개 변수 이름만 바꾸는 것은 오버로딩이라고 볼 수 없음
  • 다른 생성자 호출(this())
    • 생성자 오버로딩이 많아질 경우 생성자 간의 중복된 코드 발생
    • 이 경우 필드 초기화한 내용은 한 생성자에만 집중적으로 작성하고 나머지 생성자는 초기화 내용을 가지고 있는 생성자를 호출하는 방법으로 개선
    • 생성자에서 다른 생성자 호출할 때 this() 코드 사용
    • this()는 자신의 다른 생성자를 호출하는 코드, 반드시 생성자 첫줄에서만 허용
public Cat(String name, String breed) {
        this.name = name;
        this.breed = breed;
}

public Cat(String name, String breed, int age) {
        this(name, breed);
        this.age = age;
}


메서드(Method)

  • 객체의 동작에 해당하는 중괄호 블록
  • 메서드 호출 => 중괄호 블록 내 모든 코드 실행
  • 용도
    • 필드 읽고 수정
    • 다른 객체 생성해서 다양한 기능 수행
    • 객체 간의 데이터 전달 수단
    • 이외 다양한 행동 구현
  • 메서드 선언
[접근제한자] [기타제어자] 반환자료형 메서드명(매개변수) {
    [return 리턴값;]
}
  • 메서드 선언부 = 메서드 시그너처

  • 리턴 타입

    • 메서드가 실행 후 리턴하는 값의 타입
    • 메서드 실행 후 결과를 호출한 곳에 넘겨줄 경우에는 리턴 값이 있어야 함
    • 리턴 값의 타입은 선언부의 반환자료형과 동일해야 함
    • 리턴 타입이 있다고 해서 반드시 리턴값을 변수에 저장할 필요 없음
    • void 타입의 경우 리턴문 없이 사용 가능
  • 매개 변수의 수를 모를 경우

    • 매개 변수를 배열 타입으로 선언
    • … 으로 선언후 리스트 나열
public int sum(int ... args) {
        return IntStream.of(args).sum();
}
  • 메서드 호출
  • 메서드는 클래스 내/외부의 호출에 의해 실행
    • 클래스 외부에서 호출할 경우 우선 클래스로부터 객체 생성
    • 클래스 참조변수 = new 클래스(매개값); * 참조변수.메서드(매개값); (리턴값 없거나, 받지 않을 경우) * 타입 변수 = 참조변수.메서드(매개값); (리턴값 받고 싶을 때)
  • 메서드 오버로딩
    • 클래스 내에 같은 이름의 메서드를 여러 개 선언하는 것
    • 매개 변수의 타입, 개수, 순서 중 하나가 달라야 한다 (시그니처가 달라야 한다)

PMM(Percona Monitoring and Management) 설치

PMM 이란?

  • PMM은 서버/클라이언트로 구성되어 MySQL(MariaDB), MongoDB, OS(Linux)를 모니터링 할 수 있는 플랫폼
  • PMM Server: PMM Client에서 수집된 데이터를 저장하겨 웹기반의 대시보드와 그래프 표시
  • PMM Client: 모니터링 대상 서버에 설치되는 에이전트로 서버정보, DB정보, 쿼리정보 등을 수집하여 PMM Server에 전달

설치

  • 테스트 목적으로는 한 대의 서버에 Server/Client를 모두 설치하나 실제 운영상에서는 분리하는 것이 좋음

설치환경

  • CentOS 7
  • Docker

Docker 설치

  • PMM Server는 Docker 이미지로 배포되므로 Docker 설치가 필요하다.

이전 버전의 Docker 삭제 (필요시)

> sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-selinux \
                  docker-engine-selinux \
                  docker-engine

스크립트를 활용한 Docker 설치

> wget -qO- https://get.docker.com/ | sh

PMM Server 설치

  • Docker에서 이미지 받아오기
> docker pull percona/pmm-server
  • 이미지 확인
> docker images | grep pmm
  • PMM 데이터 컨테이너 생성
> docker create -v /opt/prometheus/data -v /opt/consul-data -v /var/lib/mysql --name pmm-data  percona/pmm-server /bin/true
  • PMM 서버 컨테이너 생성 / 실행 (포트 변경이 필요하다면 80:80을 사용할포트:80으로 변경)
> docker run -d  -p 80:80 --volumes-from pmm-data  --name pmm-server  --restart always  percona/pmm-server
  • 생성된 컨테이너 확인
> docker ps -a

PMM Client 설치

> rpm -ivh https://www.percona.com/downloads/pmm-client/1.8.1/binary/redhat/7/x86_64/pmm-client-1.8.1-1.x86_64.rpm
  • 다운로드 링크에서해당 OS의 최신버전 URL을 따와서 위와같이 실행
  • 혹은, 파일을 직업 올린 후에 해당 위치에서 위의 명령어 실행

Server 등록하기

> pmm-admin config --client-name 클라이언트이름 --server 서버아이피:포트
# --bind-address 사설아이피 --client-address 공용아이피 (방화벽 설정이 되어있을 경우)

모니터링 서비스 등록하기

  • 모니터링 서비스
    • linux:metrics > 일반 시스템 모니터링
    • mysql:metrics > MySQL 모니터링
    • mysql:queries > MySQL 쿼리 모니터링
    • mongodb:metrics > MongoDB 모니터링
    • mysql > linux:metrics + mysql:metrics + mysql:queries
    • mongodb > linux:metrics + mongodb:metrics
> pmm-admin add mysql --user root --password 루트비밀번호
  • 등록 확인하기
> pmm-admin list

설치 확인


Gmail Alert 설정

> docker ps
# 결과
CONTAINER ID        IMAGE                COMMAND                CREATED             STATUS              PORTS                           NAMES
d99b87dbc8f1        percona/pmm-server   "/opt/entrypoint.sh"   13 days ago         Up About a minute   443/tcp, 0.0.0.0:9001->80/tcp   pmm-server
> docker exec -it CONTAINER ID /bin/bash
# pmm-server 컨테이너의 bash 쉘에 접속

> vi /etc/grafana/grafana.ini

grafana 설정 수정

[smtp]
enabled = true
host = smtp.gmail.com:465
user = your.gmail.account@gmail.com
password = yourpassword
cert_file =
key_file =
skip_verify = false
from_address = admin@grafana.localhost
from_name = PMM
  • ctrl + d 또는 ctrl + p + ctrl + q 로 도커 컨테이너 쉘 종료
  • pmm-server 도커 컨테이너 재시작
> docker restart pmm-server
  • Grafana 대쉬보드 접속 → 좌측 상단 Grafana 아이콘 클릭 → 드롭다운 메뉴에서 Alerting 클릭
  • Create an alert notification 클릭
Name: Gmail Alert
Type: email
Send on all alerts: Check
Include Images: Check
Email Address: 받을 사람 주소 목록 (여러개일 경우 ; 로 구분)

Example (Grafana Dashboard)




Zabbix Slack 연동

스크립트

#!/bin/sh

webhook_url=$1
message=$2

curl -k -X POST -d "payload={\"username\":\"zabbix\", \"text\":\"$message\"}" $webhook_url
  • 스크립트 위치는 zabbix_server.conf 설정 파일에서 AlertScriptPath에 설정된 위치
  • 해당 위치에 쉘스크립트를 넣어놓고 진행

Slack Web Hook Url

  • Slack App > Incoming WebHooks
  • Add Configuration > Choose Channel > Add Incoming WebHooks integration > url 확인

미디어 타입 설정

  • 관리 - 미디어 타입 - 연락방법 작성
  • 첨부 그림 참고




내용

  • 이름: 미디어 타입 명
  • 종류: 스크립트
  • 스크립트 이름: 작성한 스크립트명 (예: zabbix-slack.sh)
  • 스크립트 파라미터
  • 파라미터1: Slack WebHook URL
  • 파라미터2: {ALERT.MESSAGE} (메시지 내용 매크로)



Zabbix Java Gateway / JMX

zabbix-java-gateway 설치

# Zabbix Server 설치된 곳에서 설치
> yum install -y zabbix-java-gateway

catalina-jmx-remote.jar 설치

# 모니터링 대상 호스트에서 설치
# TOMCAT 8
# TOMCAT_HOME\lib 폴더
> wget http://apache.mirror.cdnetworks.com/tomcat/tomcat-8/v8.0.49/bin/extras/catalina-jmx-remote.jar

bin/catalina.sh 수정

# TOMCAT_HOME\bin\catalina.sh
# JAVA_OPTS 하단에 추가
 
JAVA_OPTS="$JAVA_OPTS
    -Dcom.sun.management.jmxremote \
    -Djava.rmi.server.hostname=HOST_SERVER_IP \
    -Dcom.sun.management.jmxremote.authenticate=false \
    -Dcom.sun.management.jmxremote.ssl=false
  "

conf/server.xml 수정

<Server port="8005" shutdown="SHUTDOWN">
...
  <Listener className="org.apache.catalina.mbeans.JmxRemoteLifecycleListener"
    rmiRegistryPortPlatform="12345" rmiServerPortPlatform="12346"/>
...
</Server>

방화벽 설정

# CentOS 7
firewall-cmd --permanent --add-port=12345/tcp
firewall-cmd --permanent --add-port=12346/tcp
firewall-cmd --reload
systemctl restart firewalld
 
# CentOS 6
vi /etc/sysconfig/iptables
 
-A INPUT -p tcp -m tcp --dport 12345 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 12346 -j ACCEPT
# 포트 변경시 그에 맞춰 변경
 
service iptables restart

Zabbix Server Install Manual (자빅스 서버 설치 매뉴얼)


Zabbix

  • 네트워크나 서버(가상)등을 포함한 서비스들을 감시하고 실시간으로 자원을 체크하여 관리자에게 신속히 알리기위한 네트워크 관리 솔루션 소프트웨어 입니다.
  • zabbix 에이전트를 Unix, Linux, Windows 등의 OS에 설치하여 CPU,MEM,파일시스템(용량),특정 TCP 등을 포함한 많은 정보를 포함하여 감시를 할수 있으며 장애대비 모니터링으로써도 탁월합니다.

설치환경

  • OS: CentOS 7 64bit 1708
  • Kernel: 3.10
  • Zabbix: 3.4
  • mariadb: 5.5.56
  • php: 5.4.16
  • Zabbix-server-mysql: 3.4.5
  • zabbix-web-mysql.noarch: 3.4.5

Zabbix 설치

Zabbix Server

Yum 설치를 위한 EPEL 저장소 추가

# yum install epel-release -y
 
Installed:
epel-release.noarch 0:7-9
 
Complete!

Zabbix Package 설치

# rpm -ivh http://repo.zabbix.com/zabbix/3.4/rhel/7/x86_64/zabbix-release-3.4-1.el7.centos.noarch.rpm
 
Updating / installing...
1:zabbix-release-3.4-1.el7 ################################# [100%]############### webtest11.com End #################

[Zabbix-DB, Zabbix-Web / DB(mariadb) / WEB(Apache) / PHP] yum 으로 한번에 설치

# yum -y install zabbix-server-mysql zabbix-web-mysql mysql mariadb-server httpd php

DB 실행 및 설정

DB 실행 및 프로세스 확인

# systemctl start mariadb
# systemctl enable mariadb
 
Created symlink from /etc/systemd/system/multi-user.target.wants/mariadb.service to /usr/lib/systemd/system/mariadb.service.
 
# ps -ef | grep mysql
 
mysql 11918 1 0 12:09 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe --basedir=/usr
mysql 12080 11918 1 12:-0 ? 00:00:00 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --log-error=/var/log/mariadb/mariadb.log --pid-file=/var/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
root 12134 11523 0 12:09 tty1 00:00:00 grep --color=auto mysql
 

기본설정 (DB 구동된 상태)

# mysql_secure_installation
 
Enter current password for root (enter for none): (패스워드 입력)
Remove anonymous users? [Y/n] Y
Disallow root login remotely? [Y/n] Y
Remove test database and access to it? [Y/n] Y
Reload privilege tables now? [Y/n] Y

DB 접속 및 설정

  • 프록시는 생략 가능
# mysql -u root -p
Enter password: (패스워드 입력)
 
MariaDB [(none)]> create database zabbix_server;
MariaDB [(none)]> grant all privileges on zabbix_server.* to zabbix@localhost identified by 'ironman';
 
MariaDB [(none)]> create database zabbix_proxy;
MariaDB [(none)]> grant all privileges on zabbix_proxy.* to zabbix@localhost identified by 'ironman';
 
MariaDB [(none)]> flush privileges;
 
MariaDB [(none)]> exit;

Zabbix 테이블값 정보 데이터베이스에 적용

# cd /usr/share/doc/zabbix-server-mysql-3.4.5
# gunzip create.sql.gz
# ls
AUTHORS COPYING ChangeLog NEWS README create.sql (해당파일)
# mysql -u root -p zbx_server < create.sql
Enter password: (DB ROOT 패스워드 입력)
Zabbix Proxy
  • 프록시 생략 가능
# cd /usr/share/doc/zabbix-proxy-mysql-3.4.6
# gunzip schema.sql.gz
# ls
AUTHORS COPYING ChangeLog NEWS README schema.sql (해당파일)
# mysql -u root -p zbx_proxy < schema.sql
Enter password: (DB ROOT 패스워드 입력)

Config 설정

Zabix Config 설정

# vi /etc/zabbix/zabbix_server.conf
 
(맨 아래 추가)
 
DBHost=localhost
DBName=zabbix_server
DBUser=zabbix
DBPassword=ironman
 
:wq (저장)
Zabbix Proxy
  • 프록시 생략 가능
# vi /etc/zabbix/zabbix_proxy.conf
 
(맨 아래 추가)
 
# 0 - active mode
# 1 - passive mode
ProxyMode=0
Server=[서버 아이피 입력]
Hostname=[원하는 호스트명 입력 예) Zabbix proxy]
DBName=zabbix_proxy
DBUser=zabbix
DBPassword=ironman

PHP Config 설정

# vi /etc/php.ini
max_execution_time = 600
max_input_time = 600
memory_limit = 256M
post_max_size = 32M
upload_max_filesize = 16M
date.timezone = Asia/Seoul (앞에 ; 주석제거후 한국시간으로 설정)
:wq (저장)

iptables 방화벽 포트 허용 및 재시작

# CentOS 7
firewall-cmd --permanent --add-port=10050/tcp
firewall-cmd --permanent --add-port=10051/tcp
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --reload
systemctl restart firewalld
 
# CentOS 6
vi /etc/sysconfig/iptables
 
# Zabbix Agent Port
-A INPUT -p tcp -m tcp --dport 10050 -j ACCEPT
# Zabbix Server Port
-A INPUT -p tcp -m tcp --dport 10051 -j ACCEPT
# Zabbix Web Port
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
# 포트 변경시 그에 맞춰 변경
 
service iptables restart

Apache & Zabbix 서버 구동

Apache Server 구동 및 재부팅시 자동시작 처리

#systemctl start httpd
#systemctl enable httpd

Zabbix Server 구동 및 재부팅시 자동시작 처리

#systemctl start zabbix-server
#systemctl enable zabbix-server

Zabbix 서버 웹으로 접속

  • http://localhost/zabbix
  • 하단의 설정 DB설정/server details 설정 후
  • 관리자 페이지 로그인(admin/zabbix)
  • VM일경우 ifconfig를 통해 inet 주소/zabbix, 연결이 안될경우 NAT 혹은 BRIDGE 방식 등의 관련하여 VM에서 웹서버 접속하는 법 찾기

Configure DB connection

  • 웹 관리자 페이지의 DB 설정을 위의 mariaDB에서 zabbix-server 설정한 것과 동일하게 설정
  • Database type: MySQL
  • Database host: localhost
  • Database port: 0
  • Database name: zabbix_server
  • User: zabbix
  • Password: ironman

Zabbix server details

  • Host: localhost
  • Port: 10051
  • Name: (웹 페이지 접속시 보여주고 싶은 메인페이지 명)

Trouble Shooting

아파치 80 Port 막혀서 웹페이지 안 뜰 경우

# 80 포트 방화벽 추가 확인
# 방화벽 확인
netstat -nlp | grep 80
 
# iptables 등록 확인
iptables -nL | grep 80
 
# 등록되어있을 경우 포트 변경
vi /etc/httpd/con/httpd.conf
Listen 다른 포트
# 다른 포트 방화벽 등록
위의 방화벽 포트 허용 및 재시작 부분 참고

SElinux

# SElinux 해제
vi /etc/sysconfig/selinux
SELINUX=disabled
 
reboot
 
# reboot 해도 SElinux 활성화 되어있을 경우
vi /etc/grub.conf
kernel ..., selinux=0 # 맨뒤에 selinux=0 추가
 
# SElinux 활성화 된 상태에서 등록
 
vi /etc/zabbix/zabbix_agent_t.te
 
# 아래 내용 신규 생성된 파일에 추가 후 저장/종료 (:wq)
module local-zabbix 1.0;
 
require {
    type zabbix_agent_t;
    class process setrlimit;
}
 
#============= zabbix_agent_t ==============
allow zabbix_agent_t self:process setrlimit;
 
:wq
 
semanage permissive -a zabbix_agent_t

Zabbix Server Install Manual (자빅스 서버 설치 매뉴얼)


Zabbix

  • 네트워크나 서버(가상)등을 포함한 서비스들을 감시하고 실시간으로 자원을 체크하여 관리자에게 신속히 알리기위한 네트워크 관리 솔루션 소프트웨어 입니다.
  • zabbix 에이전트를 Unix, Linux, Windows 등의 OS에 설치하여 CPU,MEM,파일시스템(용량),특정 TCP 등을 포함한 많은 정보를 포함하여 감시를 할수 있으며 장애대비 모니터링으로써도 탁월합니다.

설치환경

  • OS: CentOS 7 64bit 1708
  • Kernel: 3.10
  • Zabbix: 3.4
  • mariadb: 5.5.56
  • php: 5.4.16
  • Zabbix-server-mysql: 3.4.5
  • zabbix-web-mysql.noarch: 3.4.5

Zabbix 설치

Zabbix Server

Yum 설치를 위한 EPEL 저장소 추가

# yum install epel-release -y
 
Installed:
epel-release.noarch 0:7-9
 
Complete!

Zabbix Package 설치

# rpm -ivh http://repo.zabbix.com/zabbix/3.4/rhel/7/x86_64/zabbix-release-3.4-1.el7.centos.noarch.rpm
 
Updating / installing...
1:zabbix-release-3.4-1.el7 ################################# [100%]############### webtest11.com End #################

[Zabbix-DB, Zabbix-Web / DB(mariadb) / WEB(Apache) / PHP] yum 으로 한번에 설치

# yum -y install zabbix-server-mysql zabbix-web-mysql mysql mariadb-server httpd php

DB 실행 및 설정

DB 실행 및 프로세스 확인

# systemctl start mariadb
# systemctl enable mariadb
 
Created symlink from /etc/systemd/system/multi-user.target.wants/mariadb.service to /usr/lib/systemd/system/mariadb.service.
 
# ps -ef | grep mysql
 
mysql 11918 1 0 12:09 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe --basedir=/usr
mysql 12080 11918 1 12:-0 ? 00:00:00 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --log-error=/var/log/mariadb/mariadb.log --pid-file=/var/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
root 12134 11523 0 12:09 tty1 00:00:00 grep --color=auto mysql
 

기본설정 (DB 구동된 상태)

# mysql_secure_installation
 
Enter current password for root (enter for none): (패스워드 입력)
Remove anonymous users? [Y/n] Y
Disallow root login remotely? [Y/n] Y
Remove test database and access to it? [Y/n] Y
Reload privilege tables now? [Y/n] Y

DB 접속 및 설정

  • 프록시는 생략 가능
# mysql -u root -p
Enter password: (패스워드 입력)
 
MariaDB [(none)]> create database zabbix_server;
MariaDB [(none)]> grant all privileges on zabbix_server.* to zabbix@localhost identified by 'ironman';
 
MariaDB [(none)]> create database zabbix_proxy;
MariaDB [(none)]> grant all privileges on zabbix_proxy.* to zabbix@localhost identified by 'ironman';
 
MariaDB [(none)]> flush privileges;
 
MariaDB [(none)]> exit;

Zabbix 테이블값 정보 데이터베이스에 적용

# cd /usr/share/doc/zabbix-server-mysql-3.4.5
# gunzip create.sql.gz
# ls
AUTHORS COPYING ChangeLog NEWS README create.sql (해당파일)
# mysql -u root -p zbx_server < create.sql
Enter password: (DB ROOT 패스워드 입력)
Zabbix Proxy
  • 프록시 생략 가능
# cd /usr/share/doc/zabbix-proxy-mysql-3.4.6
# gunzip schema.sql.gz
# ls
AUTHORS COPYING ChangeLog NEWS README schema.sql (해당파일)
# mysql -u root -p zbx_proxy < schema.sql
Enter password: (DB ROOT 패스워드 입력)

Config 설정

Zabix Config 설정

# vi /etc/zabbix/zabbix_server.conf
 
(맨 아래 추가)
 
DBHost=localhost
DBName=zabbix_server
DBUser=zabbix
DBPassword=ironman
 
:wq (저장)
Zabbix Proxy
  • 프록시 생략 가능
# vi /etc/zabbix/zabbix_proxy.conf
 
(맨 아래 추가)
 
# 0 - active mode
# 1 - passive mode
ProxyMode=0
Server=[서버 아이피 입력]
Hostname=[원하는 호스트명 입력 예) Zabbix proxy]
DBName=zabbix_proxy
DBUser=zabbix
DBPassword=ironman

PHP Config 설정

# vi /etc/php.ini
max_execution_time = 600
max_input_time = 600
memory_limit = 256M
post_max_size = 32M
upload_max_filesize = 16M
date.timezone = Asia/Seoul (앞에 ; 주석제거후 한국시간으로 설정)
:wq (저장)

iptables 방화벽 포트 허용 및 재시작

# CentOS 7
firewall-cmd --permanent --add-port=10050/tcp
firewall-cmd --permanent --add-port=10051/tcp
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --reload
systemctl restart firewalld
 
# CentOS 6
vi /etc/sysconfig/iptables
 
# Zabbix Agent Port
-A INPUT -p tcp -m tcp --dport 10050 -j ACCEPT
# Zabbix Server Port
-A INPUT -p tcp -m tcp --dport 10051 -j ACCEPT
# Zabbix Web Port
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
# 포트 변경시 그에 맞춰 변경
 
service iptables restart

Apache & Zabbix 서버 구동

Apache Server 구동 및 재부팅시 자동시작 처리

#systemctl start httpd
#systemctl enable httpd

Zabbix Server 구동 및 재부팅시 자동시작 처리

#systemctl start zabbix-server
#systemctl enable zabbix-server

Zabbix 서버 웹으로 접속

  • http://localhost/zabbix
  • 하단의 설정 DB설정/server details 설정 후
  • 관리자 페이지 로그인(admin/zabbix)
  • VM일경우 ifconfig를 통해 inet 주소/zabbix, 연결이 안될경우 NAT 혹은 BRIDGE 방식 등의 관련하여 VM에서 웹서버 접속하는 법 찾기

Configure DB connection

  • 웹 관리자 페이지의 DB 설정을 위의 mariaDB에서 zabbix-server 설정한 것과 동일하게 설정
  • Database type: MySQL
  • Database host: localhost
  • Database port: 0
  • Database name: zabbix_server
  • User: zabbix
  • Password: ironman

Zabbix server details

  • Host: localhost
  • Port: 10051
  • Name: (웹 페이지 접속시 보여주고 싶은 메인페이지 명)

Trouble Shooting

아파치 80 Port 막혀서 웹페이지 안 뜰 경우

# 80 포트 방화벽 추가 확인
# 방화벽 확인
netstat -nlp | grep 80
 
# iptables 등록 확인
iptables -nL | grep 80
 
# 등록되어있을 경우 포트 변경
vi /etc/httpd/con/httpd.conf
Listen 다른 포트
# 다른 포트 방화벽 등록
위의 방화벽 포트 허용 및 재시작 부분 참고

SElinux

# SElinux 해제
vi /etc/sysconfig/selinux
SELINUX=disabled
 
reboot
 
# reboot 해도 SElinux 활성화 되어있을 경우
vi /etc/grub.conf
kernel ..., selinux=0 # 맨뒤에 selinux=0 추가
 
# SElinux 활성화 된 상태에서 등록
 
vi /etc/zabbix/zabbix_agent_t.te
 
# 아래 내용 신규 생성된 파일에 추가 후 저장/종료 (:wq)
module local-zabbix 1.0;
 
require {
    type zabbix_agent_t;
    class process setrlimit;
}
 
#============= zabbix_agent_t ==============
allow zabbix_agent_t self:process setrlimit;
 
:wq
 
semanage permissive -a zabbix_agent_t

+ Recent posts