노드에 샤드를 할당하는 과정을 샤드 할당이라고 한다. 클러스터 매니저는 어떤 샤드를 어떤 노드에 할당하고, 언제 노드 간에 샤드 이동이 필요한지 결정한다. 샤드 할당은 다음과 같은 상황에서 발생한다.
클러스터 설정에서 cluster.routing.allocation
을 변경해서 샤드 할당 방식을 제어할 수 있다. cluster.routing.allocation
설정이 궁금하다면 공식 문서를 참고하면 된다. (https://opensearch.org/docs/latest/api-reference/cluster-settings/)
Elasticvue를 활용해서 test1
인덱스를 생성해보자. Elasticvue에서는 샤드 개수를 지정하지 않을 경우 기본적으로 프라이머리 샤드 1개, 프라이머리 샤드 당 레플리카 샤드 1개가 생성된다.
test1
인덱스는 데이터 노드 2개에 걸쳐 저장됐고, 프라이머리 샤드와 레플리카 샤드 쌍은 서로 다른 데이터 노드에 배치된 것을 확인할 수 있다.
프라이머리 샤드 5개, 프라이머리 샤드 당 레플리카 샤드 5개로 test2
인덱스를 생성해보자. 총 30개의 샤드가 생성될 것이다.
프라이머리 샤드와 레플리카 샤드 쌍이 서로 다른 데이터 노드에 배치된 것을 확인할 수 있다.
그런데 unassigned는 무엇일까? 샤드의 상태는 다음과 같이 크게 4가지로 구분할 수 있다.
unassigned
: 샤드가 어떤 노드에도 할당되지 않은 상태initializing
: 샤드를 노드 메모리에 적재중인 상태 (메모리에 모두 적재된 것이 아니므로, 샤드 사용 불가능)started
: 샤드가 메모리에 모두 적재되어 사용 가능한 상태 (프라이머리 샤드가 started 상태가 되어야 레플리카 샤드 할당 가능)relocating
: 샤드가 다른 노드로 재배치중인 상태샤드의 상태에 따라 인덱스의 상태 또한 결정된다. Elasticvue의 INDICES 탭에서 Health 컬럼을 보면 인덱스의 상태를 확인할 수 있다.
red
: 한 개 이상의 프라이머리 샤드가 unassigned 된 경우 (인덱스 읽기/쓰기 모두 불가능)yellow
: 프라이머리 샤드는 모두 정상적으로 노드에 할당되었지만, 한 개 이상의 레플리카 샤드가 unassinged 된 경우 (인덱스 읽기/쓰기 모두 가능하지만, 장애 발생시 인덱스를 사용하지 못할 수 있음)green
: 프라이머리 샤드와 레플리카 샤드 모두 정상적으로 노드에 할당된 경우test2
인덱스의 경우 프라이머리 샤드는 모두 정상적으로 노드에 할당되었기 때문에 yellow 상태인 것을 확인할 수 있다.
test2
인덱스의 레플리카 샤드 일부가 정상적으로 노드에 할당되지 못한 이유는 무엇일까? GET /_cluster/allocation/explain
API로 샤드가 할당되지 않은 이유를 알 수 있다. Elasticvue REST 클라이언트를 활용해서 다음 API를 호출해보자.
$ curl -XGET $OPENSEARCH_REST_API/_cluster/allocation/explain?pretty=true
explanation
을 보면 a copy of this shard is already allocated to this node [[test2][3], node[OpwmNmeiQSCd19yKmZfKfQ], [R], s[STARTED], a[id=_FKc6P1oRuKDZqO6Bpccyw]]
라고 설명하고 있다. 즉 프라이머리 샤드의 레플리카 샤드가 노드에 이미 존재하는 경우, 동일한 노드에 또 다른 레플리카 샤드를 할당할 수 없다는 뜻이다. OpenSearch가 프라이머리 샤드와 동일한 노드에 레플리카 샤드를 할당하지 않고, 동일한 노드에 동일한 프라이머리 샤드의 복제본을 두 개 이상 할당하지 않기 때문이다.
4.1.3과 같은 이유로 발생한 unassigned 샤드 문제는 두 가지 방법으로 해결할 수 있다.
먼저 인덱스의 레플리카 수를 줄여서 문제를 해결해보자. 다음 요청으로 test2
인덱스의 레플리카 샤드 개수를 프라이머리 샤드 당 1개로 변경할 수 있다.
$ curl -XPUT "$OPENSEARCH_REST_API/test2/_settings?pretty=true" \
-H "Content-Type: application/json" \
-d '
{
"number_of_replicas": 1
}
'
unassigned 샤드가 사라지고, 인덱스의 상태도 green으로 변경된 것을 확인할 수 있다.
이번에는 클러스터에 데이터 노드를 추가해서 문제를 해결해보자. 먼저 test2
인덱스의 레플리카 샤드 개수를 프라이머리 샤드 당 2개로 늘려서 문제 상황을 재현해보자.
$ curl -XPUT "$OPENSEARCH_REST_API/test2/_settings?pretty=true" \
-H "Content-Type: application/json" \
-d '
{
"number_of_replicas": 2
}
'
총 5개의 레플리카 샤드가 unassigned 상태인 것을 확인할 수 있다.
AMI를 활용해서 EC2 인스턴스를 1개 더 생성하고, OpenSearch config를 다음과 같이 수정해주자.
plugins.security.disabled: true
cluster.name: opensearch-cluster
node.name: opensearch-d3
node.roles: [ data, ingest ]
network.host: 0.0.0.0
discovery.seed_hosts: ["<CLUSTER_MANAGER_NODE_PUBLIC_IP>"]
노드를 재시작하고 Elasticvue를 확인해보면 클러스터에 opensearch-d3
노드가 추가된 것을 확인할 수 있다.
unassigned 상태였던 모든 레플리카 샤드가 opensearch-d3
노드에 자동으로 재배치되면서 인덱스 상태도 green으로 정상화되었다.
앞서 본 것처럼 기본적으로 OpenSearch 클러스터는 샤드 할당과 재배치를 자동으로 수행한다. 수동으로 샤드를 재배치하고 싶다면 reroute API를 사용하면 된다. 다음 요청으로 test1
인덱스의 프라이머리 샤드를 opensearch-d2
노드에서 opensearch-d3
노드로 재배치해보자.
$ curl -XPOST "$OPENSEARCH_REST_API/_cluster/reroute?pretty=true" \
-H "Content-Type: application/json" \
-d '
{
"commands": [
{
"move": {
"index": "test1",
"shard": 0,
"from_node": "opensearch-d2",
"to_node": "opensearch-d3"
}
}
]
}
'
응답 본문의 state.routing_table.indices.test1.shards.0
을 보면 프라이머리 샤드가 relocating 상태로 바뀐 것을 확인할 수 있다.
[
{
"state": "RELOCATING",
"primary": true,
"node": "OpwmNmeiQSCd19yKmZfKfQ",
"relocating_node": "m0-xttC6SK6V7e34R5IOSw",
"shard": 0,
"index": "test1",
"expected_shard_size_in_bytes": 4327,
"allocation_id": {
"id": "y0nEcSbfSiGlXKPclHu7Hg",
"relocation_id": "pvTwn9XzRhyJpOt_x6SiZQ"
}
},
{
"state": "STARTED",
"primary": false,
"node": "2usKiujOSGSH5JwEYRFGrA",
"relocating_node": null,
"shard": 0,
"index": "test1",
"allocation_id": {
"id": "cxX3hEamRsGfFF95zhHoEg"
}
}
]
성공적으로 샤드가 재배치될 경우 샤드 상태는 started로 바뀐다.
다음과 같이 Elasticvue에서 GUI로 샤드를 재배치할 수도 있다.
의도적이든 아니든 어떤 이유로든 노드가 클러스터를 이탈하게 되면, 클러스터 매니저는 다음과 같은 과정을 수행한다.
위와 같은 과정 덕분에 노드가 클러스터를 이탈하더라도 모든 샤드를 최대한 빨리 복제 및 복구하여 클러스터의 데이터 손실을 막을 수 있다. 그러나 2~3에 해당하는 샤드 셔플(Shard shuffle)은 클러스터에 큰 부하를 주는 작업이므로, 이탈한 노드가 바로 클러스터에 복귀하는 상황을 가정해서 일정 시간 샤드 셔플을 미루는 것이 좋다. index.unassigned.node_left.delayed_timeout
을 설정해서 샤드 셔플을 미룰 수 있으며, 기본 설정은 1m이다. delayed_timeout
은 레플리카 샤드를 프라이머리 샤드로 승격하는 것에는 영향을 주지 않는다.
다음과 같은 상태에서 opensearch-d2
노드를 멈춰보자. EC2 인스턴스를 stop하거나, $ sudo systemctl stop opensearch.service
를 실행하면 된다.
노드가 이탈한 즉시 opensearch-d3
의 레플리카 샤드 일부가 프라이머리 샤드로 승격된 것을 확인할 수 있다. delayed_timeout
설정 때문에 unassigned 레플리카 샤드는 아직 재배치되지 않았다.
기본 delayed_timeout
설정대로 약 1분 정도 기다리면 test1
인덱스의 unassigned 레플리카 샤드가 opensearch-d3
로 재배치된 것을 확인할 수 있다. test2
인덱스의 unassigned 레플리카 샤드는 노드 부족으로 인해 재배치되지 못했다.
opensearch-d2
노드를 클러스터에 복귀시켜보자. test2
인덱스의 unassigned 레플리카 샤드가 opensearch-d2
노드에 재배치된 것을 확인할 수 있다.
이번에는 opensearch-d3
노드를 멈추고, 1분 안에 클러스터에 복귀시켜보자. test1 인덱스의 unassigned 레플리카 샤드가 opensearch-d3
노드에 재배치된 것을 확인할 수 있다. opensearch-d3
노드에는 이미 test1
인덱스의 0번 샤드 정보가 디스크에 저장되어 있기 때문에 opensearch-d2
노드보다 더 빠르게 레플리카 샤드를 재배치할 수 있다.
인덱스에 도큐먼트를 제한 없이 저장한다면, 인덱스의 크기는 무한히 커질 수 밖에 없다. 인덱스의 크기가 너무 커질 경우 성능 문제가 발생할 수 있으므로, 주기적으로 인덱스를 나눠주는 작업이 필요하다. OpenSearch에서는 ISM(Index State Management)을 통해 자동으로 인덱스의 라이프사이클을 관리할 수 있는데, 예를 들면 다음과 같은 규칙을 설정할 수 있다.
ISM은 내부적으로 rollover와 shrink라는 개념을 사용해서 인덱스를 관리한다.
인덱스가 특정 조건에 도달했을 때 새로운 인덱스를 생성하는 API다. ISM 템플릿에 rollover 조건을 설정하면 주기적으로 rollover 조건을 체크하고, 새로운 인덱스를 생성해준다. 본 강의에서는 rollover API를 직접 호출해서 수동으로 인덱스를 rollover해볼 것이다.
rollover를 사용하려면 인덱스 alias 설정이 필요하다. 인덱스 alias는 여러 인덱스를 가리킬 수 있는 가상의 인덱스 이름으로, 여러 인덱스를 동일한 그룹으로 묶고 싶을 때 사용하면 된다. alias가 가리키는 인덱스는 언제든지 변경할 수 있으며, alias를 통해 여러 인덱스에 걸쳐 분산 저장된 도큐먼트를 한 번에 쿼리할 수도 있다. 예를 들어, 월 기준으로 인덱스에 로그를 저장하고, 최근 두 달 동안의 로그를 자주 쿼리하는 경우 last_2_months
라는 alias를 생성하고, 매월 해당 alias가 가리키는 인덱스를 업데이트하면 된다. rollover는 인덱스 alias에 대해 수행된다. 즉 인덱스 alias를 rollover하면, 해당 alias에 새로운 인덱스가 생성되는 것이다.
rollver API 실습을 위해 Elasticvue에서 my-index-000001
인덱스를 생성하자. 인덱스 alias를 rollover할 때 기존 인덱스 이름이 my-index-000001
과 같이 하이픈과 6자리 숫자 조합으로 끝날 경우 새로운 인덱스의 이름은 my-index-000002
처럼 자동으로 숫자가 증가되어 설정된다. 인덱스 이름이 위의 형식과 맞지 않을 경우 target-index
필드를 통해 새로 만들 인덱스 이름을 지정해줘야 한다.
다음 요청으로 my-index-000001
인덱스에 alias 설정을 추가할 수 있다. is_write_index
를 true
로 설정해야 해당 인덱스에 대한 쓰기 작업이 허용되며, 인덱스 alias의 여러 인덱스 중 하나의 인덱스에만 is_write_index
를 true
로 설정할 수 있다. 즉 인덱스 alias는 쓰기 가능한 인덱스를 한 개만 가질 수 있다.
$ curl -XPOST "$OPENSEARCH_REST_API/_aliases?pretty=true" \
-H "Content-Type: application/json" \
-d '
{
"actions": [
{
"add": {
"index": "my-index-000001",
"alias": "alias1",
"is_write_index": true
}
}
]
}
'
다음과 같은 조건으로 alias1
을 rollover해보자. 현재 alias1
에 인덱싱된 도큐먼트가 없기 때문에 rollover되지 않은 것을 확인할 수 있다.
$ curl -XPOST "$OPENSEARCH_REST_API/alias1/_rollover?pretty=true" \
-H "Content-Type: application/json" \
-d '
{
"conditions": {
"max_age": "30d",
"max_docs": 1,
"max_size": "10gb"
}
}
'
다음 요청으로 alias1
에 도큐먼트를 인덱싱해보자. my-index-000001
인덱스에 도큐먼트가 인덱싱된 것을 응답 본문에서 확인할 수 있다. PUT /alias/_doc
API를 사용하면 OpenSearch는 해당 인덱스 alias에 포함된 인덱스 중 쓰기 가능한 인덱스로 요청을 라우팅하기 때문이다.
$ curl -XPUT "$OPENSEARCH_REST_API/alias1/_doc/1?pretty=true" \
-H "Content-Type: application/json" \
-d '
{
"text": "Hello, world!"
}
'
다시 alias1
을 rollover해보자. max_docs
조건을 만족했기 때문에 rollover가 되고, 새로운 my-index-000002
인덱스가 생성된 것을 확인할 수 있다.
$ curl -XPOST "$OPENSEARCH_REST_API/alias1/_rollover?pretty=true" \
-H "Content-Type: application/json" \
-d '
{
"conditions": {
"max_age": "30d",
"max_docs": 1,
"max_size": "10gb"
}
}
'
Elasticvue에서 Show info
버튼을 눌러서 인덱스 정보를 확인해보자. is_write_index
설정이 변경된 것을 확인할 수 있다. 즉 앞으로 alias1
에 대한 쓰기 작업은 my-index-000002
인덱스만 참여하고, alias1
에 대한 검색 작업은 my-index-000001
과 my-index-000002
인덱스 모두 참여하게 된다. is_write_index
은 인덱스 alias 수준의 설정이므로, POST /my-index-000001/_doc
으로 my-index-000001
에 직접 인덱싱을 요청할 경우 여전히 정상적으로 처리된다.
다음과 같이 alias1
에 도큐먼트를 인덱싱하면 my-index-000002
에 인덱싱된 것을 응답 본문에서 확인할 수 있다.
$ curl -XPUT "$OPENSEARCH_REST_API/alias1/_doc/2?pretty=true" \
-H "Content-Type: application/json" \
-d '
{
"text": "What is your name?"
}
'
alias1
에 대해 검색을 요청하면, alias1
에 포함된 모든 인덱스에 대해 검색 작업이 수행된 것을 확인할 수 있다.
shrink API는 인덱스의 프라이머리 샤드 개수를 줄여준다. 내부적으로 shrink API는 기존 인덱스의 모든 데이터를 프라이머리 샤드 개수가 더 적게 설정된 새로운 인덱스로 옮겨서 샤드 병합을 수행한다. 자주 사용하지 않는 인덱스의 경우 shrink API로 샤드 개수를 줄여서 리소스 사용량을 최소화하는 것이 좋다.
shrink API를 사용하기 위한 전제 조건은 다음과 같다.
p0
, p1
, r0
, r1
샤드가 존재하는 경우, p0
, r1
이 동일한 노드에 있으면 조건을 충족한다. 반드시 모든 샤드가 프라이머리 샤드일 필요는 없다.test2
인덱스 설정을 수정해서 shrink API를 사용할 수 있도록 만들어보자. 다음 요청은 test2
인덱스에 대해 3가지 작업을 수행한다.
$ curl -XPUT "$OPENSEARCH_REST_API/test2/_settings?pretty=true" \
-H "Content-Type: application/json" \
-d '
{
"settings": {
"index.number_of_replicas": 0,
"index.routing.allocation.require._name": "opensearch-d3",
"index.blocks.write": true
}
}
'
다음 요청으로 test2
인덱스를 test2-new
인덱스로 shrink해보자. shrink 작업은 원본 인덱스와 동일한 설정으로 새로운 인덱스를 생성하는데, 다음과 같이 settings 필드를 통해 일부 설정을 overwrite할 수 있다. index.routing.allocation.require.name
를 null
로 초기화해서 인덱스가 여러 노드에 분산되도록 했고, index.blocks.write
도 null
로 초기화해서 인덱스를 쓰기 가능하도록 만들었다.
$ curl -XPOST "$OPENSEARCH_REST_API/test2/_shrink/test2-new?pretty=true" \
-H "Content-Type: application/json" \
-d '
{
"settings": {
"index.number_of_shards": 2,
"index.number_of_replicas": 1,
"index.routing.allocation.require._name": null,
"index.blocks.write": null
}
}
'
핫-웜-콜드 아키텍처에서는 데이터 검색 빈도에 따라 핫/웜/콜드 노드에 저장한다. 핫-웜-콜드 아키텍처는 로그, 메트릭 및 트랜잭션 같은 시계열 데이터에 주로 사용된다. 시계열 데이터는 한 번 인덱싱되면 업데이트 될 가능성이 거의 없고, 시간이 지날수록 데이터 검색 빈도가 낮아지기 때문에 오래된 데이터를 저렴한 스토리지로 이동시켜 비용을 절감할 수 있다. 즉 처음에는 데이터를 성능이 좋고 비싼 핫 노드에 인덱싱하고, 일정 시간이 지나면 약간 느리지만 저렴한 웜 노드로 이동시키는 아키텍처를 핫-웜-콜드 아키텍처라고 부른다. 핫-웜-콜드 노드의 특징은 다음과 같다.
예를 들어 최근 로그는 활발하게 인덱싱되고 검색되기 때문에 핫 노드에, 지난주 로그는 최근 로그만큼 자주 검색되지 않기 때문에 웜 노드에, 지난달 로그는 거의 검색되지 않지만 혹시 모를 상황에 대비해서 콜드 노드에 저장할 수 있다.
노드 2개와 ISM으로 핫-웜 아키텍처를 구성해보자. opensearch-d1
은 핫 노드로, opensearch-d2
는 웜 노드로 사용할 것이다. 모든 인덱스는 처음에는 핫 노드에 저장되고, 이후 ISM에 의해 웜 노드로 이동된다. 먼저 노드에 hot
또는 warm
태그를 붙여줘야 한다.
opensearch-d1
노드 설정 파일을 다음과 같이 수정하고, 노드를 재시작하자.
node.attr.temp: hot
opensearch-d2
노드 설정 파일을 다음과 같이 수정하고, 노드를 재시작하자.
node.attr.temp: warm
temp
라는 노드 속성과 hot
, warm
과 같은 태그 값은 임의로 설정한 것이며, 다른 어떤 값이든 사용할 수 있다. 단, ISM 템플릿에서도 동일한 값을 사용해야 한다. 다음 요청으로 노드 설정이 잘 적용되었는지 확인해보자.
$ curl -XGET "$OPENSEARCH_REST_API/_cat/nodeattrs?v&h=node,attr,value&pretty=true"
인덱스를 생성하면 샤드를 무조건 핫 노드에 할당하도록 인덱스 템플릿을 생성하자. 인덱스 템플릿은 새로운 인덱스를 생성할 때 사용할 설정 값을 미리 정의해놓은 것이다. 다음과 같이 logs-template
이라는 인덱스 템플릿을 만든 경우, 앞으로 인덱스 이름이 logs-*
패턴과 일치하면 해당 인덱스는 자동으로 hot
노드에 할당된다. index.plugins.index_state_management.rollover_alias
는 해당 인덱스가 rollover할 때 사용할 인덱스 alias를 정의하는 필드다.
$ curl -XPUT "$OPENSEARCH_REST_API/_index_template/logs-template?pretty=true" \
-H "Content-Type: application/json" \
-d '
{
"index_patterns": [
"logs-*"
],
"template": {
"settings": {
"index": {
"plugins": {
"index_state_management": {
"rollover_alias": "logs"
}
},
"routing": {
"allocation": {
"require": {
"temp": "hot"
}
}
}
}
}
}
}
'
logs-test
인덱스를 생성해보자.
hot
으로 태깅된 노드가 한 개 뿐인 상황에서 레플리카 샤드를 동일한 노드에 할당할 수 없기 때문이다.$ curl -XPUT "$OPENSEARCH_REST_API/logs-test/_doc/1?pretty=true" \
-H "Content-Type: application/json" \
-d '
{
"content": "Hello, OpenSearch!"
}
'
다음과 같은 방식으로 인덱스를 관리하는 ISM 정책을 생성해보자. 테스트를 위해 index_patterns
에는 일부러 wildcard를 사용하지 않았다. 즉 logs-000001
인덱스에 대해서만 ISM 정책이 적용될 것이다.
$ curl -XPUT "$OPENSEARCH_REST_API/_plugins/_ism/policies/hot-warm?pretty=true" \
-H "Content-Type: application/json" \
-d '
{
"policy": {
"description": "hot-warm architecture",
"error_notification": null,
"default_state": "hot",
"states": [
{
"name": "hot",
"actions": [
{
"retry": {
"count": 3,
"backoff": "exponential",
"delay": "1m"
},
"rollover": {
"min_index_age": "1m"
}
}
],
"transitions": [
{
"state_name": "warm",
"conditions": {
"min_rollover_age": "5m"
}
}
]
},
{
"name": "warm",
"actions": [
{
"retry": {
"count": 3,
"backoff": "exponential",
"delay": "1m"
},
"replica_count": {
"number_of_replicas": 0
}
},
{
"retry": {
"count": 3,
"backoff": "exponential",
"delay": "1m"
},
"allocation": {
"require": {
"temp": "warm"
},
"include": {},
"exclude": {},
"wait_for": false
}
}
],
"transitions": []
}
],
"ism_template": [
{
"index_patterns": [
"logs-000001"
]
}
]
}
}
'
다음 요청으로 ISM 정책이 적용될 logs-000001
인덱스를 생성하고, logs
alias에 추가해주자.
$ curl -XPUT "$OPENSEARCH_REST_API/logs-000001?pretty=true"
-H "Content-Type: application/json" \
-d '
{
"aliases": {
"logs": {
"is_write_index": true
}
}
}
'
다음과 같이 explain API를 활용하면 해당 인덱스에 어떤 ISM 정책이 적용되었고, 현재 어떤 상태에 있는지 확인할 수 있다. 인덱스에 ISM 정책이 적용되는 데는 몇 분 정도 걸린다.
$ curl -XGET "$OPENSEARCH_REST_API/_plugins/_ism/explain/logs-000001?pretty=true"
1단계: hot-warm
정책이 logs-000001
인덱스에 성공적으로 적용되면 다음과 같은 응답 본문이 온다. 현재는 hot
상태에 있다.
2단계: logs-000001
인덱스가 rollover되면서 logs-000002
인덱스가 생성됐다.
3단계: rollover 를 성공적으로 마치고, transition 을 evaluation 했다. 하지만 아직 조건이 맞지 않아서 다음 시도를 기다릴 때 다음과 같은 응답이 나온다.
4단계: Transition 조건이 맞아 transition을 시도하고 있다. 이 과정에서 노드간 복사가 일어나므로 시간이 오래 걸린다.
완료: warm 으로의 transition 이 완료되면 다음과 같은 메세지를 받을 수 있다.
샤드 결과
logs-000001
인덱스가 warm 상태로 바뀌면서 레플리카 샤드가 없어지고, 웜 노드(opensearch-d2
)로 이동한 것을 확인할 수 있다.
ElasticSearch에서는 ILM(Index Lifecycle Management)을 통해 인덱스 라이프사이클을 관리할 수 있다. ISM과 ILM은 모두 rollover된 인덱스 기반으로 작동하며, 인덱스는 인덱스 크기, 도큐먼트 개수 등을 기준으로 rollover된다. ISM에서 핫-웜 노드 타입은 앞서 본 것처럼 노드 속성으로 구분되는 반면, ISM은 노드 타입 자체로 구분된다. 예를 들어 ElasticSearch는
node.roles: [ data_hot ]
처럼 설정하면 핫 노드로 구분할 수 있다. 또한 ISM은 핫-웜-콜드 아키텍처를 구성하기 위해 직접 템플릿을 정의해야 하지만, ILM은 기본 템플릿으로 제공된다.