작업에서 Nomad 액션 사용하기
작업에서 Nomad 액션 사용하기 (Use Nomad actions in jobs)
이 튜토리얼에서는 작업에서 Nomad 액션(Actions)을 만들고 실행해요. 액션은 작업 작성자가 작성하는 명령이며, 태스크 config와 같은 형태를 가져요.
action "hello-world" {
command = "echo"
args = ["Hello, world!"]
}
이 액션들은 jobspec 내에서 태스크(Task) 수준에 위치하며, 실행 후 변수나 대화형 입력 없이 실행돼요. 다른 실행 가능한 명령과 마찬가지로 스스로 종료되거나 실행 중인 사용자가 수동으로 종료할 때까지 실행될 수 있어요. Nomad는 작업 또는 태스크 컨텍스트에서 액션과 상호작용하기 위한 CLI 명령, API, 웹 UI를 제공해요.
출처: 문서
본문
도전 과제 (Challenge)
이 튜토리얼에서는 Redis 인스턴스를 실행하는 Nomad 작업을 수정하고, 다음과 같은 반복 가능한 Nomad 액션을 만들어요:
- 데이터베이스에서 항목을 추가하고 제거하는 일반적인 워크플로를 단순화
- 해당 항목의 속성과 Redis 인스턴스의 지연 시간을 모니터링하고 보고
- 데이터베이스의 핵심 동작 수정
전제 조건 (Prerequisites)
- Nomad v1.7.0 이상
- Nomad dev 에이전트 또는 Nomad 클러스터
- Docker가 설치되어 태스크 드라이버로 사용 가능
시작 작업 파일 만들기 (Build the starting job file)
다음 내용으로 redis-actions.nomad.hcl이라는 텍스트 파일을 만들어요.
redis-actions.nomad.hcl
job "redis-actions" {
group "cache" {
network {
port "db" {}
}
task "redis" {
driver = "docker"
config {
image = "redis:7"
ports = ["db"]
command = "/bin/sh"
args = ["-c", "redis-server --port ${NOMAD_PORT_db} & /local/db_log.sh"]
}
template {
data = <<EOF
#!/bin/sh
while true; do
echo "$(date): Current DB Size: $(redis-cli -p ${NOMAD_PORT_db} DBSIZE)"
sleep 3
done
EOF
destination = "local/db_log.sh"
perms = "0755"
}
resources {
cpu = 128
memory = 128
}
service {
name = "redis-service"
port = "db"
provider = "nomad"
check {
name = "alive"
type = "tcp"
port = "db"
interval = "10s"
timeout = "2s"
}
}
}
}
}
이 작업은 단일 Redis 인스턴스를 만들고, Nomad가 동적으로 할당하는 "db"라는 포트와 Nomad 서비스 헬스 체크를 구성해요. 태스크의 config와 template 블록은 redis 서버를 시작하고 3초마다 현재 데이터베이스 크기를 보고해요.
첫 번째 액션 작성하기 (Write your first Action)
관리 또는 alloc-exec 권한이 있는 사용자라면 실행 중인 인스턴스에 ssh로 접속해 Redis 인스턴스에 데이터를 추가할 수 있어요. 하지만 여기에는 몇 가지 단점이 있어요:
- 서로 다른 시점에 데이터를 추가하려면 인스턴스에 여러 번 ssh로 접속해야 할 수 있으며, 그 방법을 기억해야 해요. 또는 그 과정에 덜 익숙한 다른 운영자가 해야 할 수도 있어요.
- 수동 추가에 대한 감사 가능한 기록이 없어요. 워크플로를 반복하거나 확장해야 한다면 수동으로 해야 해요.
- Nomad 태스크는 관리 시크릿이나 환경 변수를 사용해 Redis에 접근할 수 있지만, 사용자는 접근할 수 없을 수 있어요. Redis에 접근하거나 Nomad 인스턴스에 ssh로 접속하기 위해 자격 증명을 수동으로 전달하면 워크플로 보안에 반복적인 접근 구멍이 생겨요.
박스에 ssh로 접속해 redis-cli SET 명령을 반복해서 실행하는 대신, 이를 jobspec 태스크의 액션으로 커밋해요. 태스크의 service 블록에 다음을 추가해요:
redis-actions.nomad.hcl
# Adds a specific key-value pair ('hello'/'world') to the Redis database
action "add-key" {
command = "/bin/sh"
args = ["-c", "redis-cli -p ${NOMAD_PORT_db} SET hello world; echo 'Key \"hello\" added with value \"world\"'"]
}
이 액션은 redis-cli 명령을 사용해 키-값 쌍을 설정한 다음 확인 메시지를 출력해요.
이제 작업을 제출해요:
$ nomad job run redis-actions.nomad.hcl
작업은 사용 가능한 새 액션과 함께 업데이트돼요. Nomad CLI를 사용해 작업, 그룹, 태스크, 액션 이름을 제공해 실행해요:
$ nomad action \
-job=redis-actions \
-group=cache \
-task=redis \
add-key
액션에 설명된 출력이 표시되어 키가 추가되었음을 알 수 있어요:
OK
Key "hello" added with value "world"
이제 실행 중인 Nomad 작업의 jobspec에 정의된 명령을 실행한 거예요.
반복 가능한 워크플로 시뮬레이션하기 (Simulate a repeatable workflow)
일정한 상태를 적용하는 액션(예: 캐시를 수동으로 지우거나 사이트를 유지보수 모드로 전환하는 액션)은 유용할 수 있어요. 하지만 이 예제에서는 반복해서 실행하고 싶은 액션을 시뮬레이션해 봐요. 고정된 키/값 대신 랜덤 문자열을 생성하도록 액션을 수정해요. 이 액션을 사용자가 가입할 때 지속적인 아티팩트가 저장되거나 다른 공개-facing 액션이 저장되는 실제 시나리오의 대리자로 생각할 수 있어요.
redis-actions.nomad.hcl
# Adds a random key/value to the Redis database
action "add-random-key" {
command = "/bin/sh"
args = ["-c", "key=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13); value=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13); redis-cli -p ${NOMAD_PORT_db} SET $key $value; echo Key $key added with value $value"]
}
이것은 데이터베이스에 랜덤 키/값을 추가하고 결과를 보고해요. 추가 기능을 설명하기 위해 키에 "temp_" 접두사를 붙인다는 점만 다른 두 번째 액션을 추가할 수 있어요:
redis-actions.nomad.hcl
# Adds a random key/value with a "temp_" prefix to the Redis database
action "add-random-temporary-key" {
command = "/bin/sh"
args = ["-c", "key=temp_$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13); value=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13); redis-cli -p ${NOMAD_PORT_db} SET $key $value; echo Key $key added with value $value"]
}
add-random-key 액션과 마찬가지로 이 새 액션은 지속적인 아티팩트를 생성하는 애플리케이션의 시뮬레이션으로 생각할 수 있어요. temp 키의 경우 실제 시나리오로는 사용자 가입 이메일 확인을 나타내는 키가 될 수 있어요. 곧 이러한 랜덤 키를 접두사에 따라 다르게 취급하는 또 다른 액션을 만들 거예요.
이 두 액션은 데이터베이스에 서로 다른 두 종류의 랜덤 데이터를 채워요. 이제 추가한 키를 볼 수 있는 액션을 만들기 위해 다음 코드 블록을 추가해요:
redis-actions.nomad.hcl
# Lists all keys currently stored in the Redis database.
action "list-keys" {
command = "/bin/sh"
args = ["-c", "redis-cli -p ${NOMAD_PORT_db} KEYS '*"]
}
이제 작업을 업데이트해요:
$ nomad job run redis-actions.nomad.hcl
Nomad 웹 UI가 실행 중이라면 작업 페이지에 접속하면 Actions 드롭다운이 표시돼요.

액션 중 하나를 선택하면 선택한 액션의 출력이 포함된 플라이아웃(fly-out)이 열려요:

다음으로 데이터베이스에서 임시 키를 지우는 "안전 밸브(safety valve)" 액션을 만드는 새 액션 블록을 추가해요. 이것은 앞서 만든 add-random-key와 add-random-temporary-key 액션이 생성한 아티팩트를 구분해 사용해요.
redis-actions.nomad.hcl
# Deletes all keys with a 'temp_' prefix
action "flush-temp-keys" {
command = "/bin/sh"
args = ["-c", <<EOF
keys_to_delete=$(redis-cli -p ${NOMAD_PORT_db} --scan --pattern 'temp_*')
if [ -n "$keys_to_delete" ]; then
# Count the number of keys to delete
deleted_count=$(echo "$keys_to_delete" | wc -l)
# Execute the delete command
echo "$keys_to_delete" | xargs redis-cli -p ${NOMAD_PORT_db} DEL
else
deleted_count=0
fi
remaining_keys=$(redis-cli -p ${NOMAD_PORT_db} DBSIZE)
echo "$deleted_count temporary keys removed; $remaining_keys keys remaining in database"
EOF
]
}
실제 시나리오에서는 예를 들어 이런 액션이 자동으로 추가된 항목을 필터링해 지우고, 남은 키 수나 삭제에 걸린 시간을 보고할 수 있어요.
이 작업은 명령줄에서 두 가지 방법으로 실행할 수 있어요:
1: 태스크가 단일 할당에서 실행 중일 때, 또는 작업을 실행하는 임의의 할당에서 액션을 수행하려는 경우:
$ nomad action \
-group=cache \
-task=redis \
-job=redis-actions \
flush-temp-keys
2: 특정한 알려진 할당에서 액션을 수행하려는 경우, 먼저 할당 ID를 가져와요:
$ nomad job status redis-actions
Nomad CLI는 jobspec에 대한 정보를 표시하며, 다음이 포함돼요:
ID = redis-actions
...
Allocations
ID Node ID Task Group Version Desired Status Created Modified
d841c716 03a56d12 cache 0 run running 5m4s ago 4m48s ago
표시된 할당에서 ID를 복사하고 다음을 실행해 flush-temp-keys 액션을 수행해요:
$ nomad action \
-alloc=d841c716 \
-job=redis-actions \
flush-temp-keys
실행 중인 액션이 할당에 의존하지 않으면 첫 번째 방법을 사용해요. 작업이 여러 Redis 인스턴스를 호스팅하고 특정 인스턴스의 캐시를 지워야 한다면 두 번째 방법을 사용해요. 실제 상황에서 선택한 방법은 목표에 따라 달라져요.
액션은 실행 중인 태스크에 영향을 줄 수 있어요 (Actions can impact the running task)
일부 액션은 애플리케이션의 현재 상태에 영향을 주지 않을 수 있어요. 예를 들어 로그 처리, 서버 통계 보고 및 전송, 토큰 해지 등이요. 하지만 이 예제에서 액션은 태스크의 활성 상태에 영향을 줘요. Redis 태스크는 DBSIZE를 db_log.sh에 쓰고 몇 초마다 로그로 기록했어요. 실행 중인 작업을 검사하고 할당 ID를 가져와요. 그런 다음 다음을 실행해요:
nomad alloc logs <alloc-id>
Nomad는 작업의 할당 ID에 대한 로그를 출력해요:
Tue Nov 28 01:23:46 UTC 2023: Current DB Size: 1
Tue Nov 28 01:23:49 UTC 2023: Current DB Size: 1
Tue Nov 28 01:23:52 UTC 2023: Current DB Size: 2
Tue Nov 28 01:23:55 UTC 2023: Current DB Size: 3
Tue Nov 28 01:23:58 UTC 2023: Current DB Size: 3
Tue Nov 28 01:24:01 UTC 2023: Current DB Size: 4
Tue Nov 28 01:24:04 UTC 2023: Current DB Size: 5
Tue Nov 28 01:24:07 UTC 2023: Current DB Size: 8
이제 애플리케이션의 더 낮은 수준의 구성 옵션에 영향을 주는 또 다른 액션을 추가해요. Redis는 디스크로의 영속화(persistence)를 켜고 끌 수 있어요. 이를 확인하고 전환하는 액션을 작성해요. 다음 액션 블록을 추가해요:
redis-actions.nomad.hcl
# Toggles saving to disk (RDB persistence). When enabled, allocation logs will indicate a save every 60 seconds.
action "toggle-save-to-disk" {
command = "/bin/sh"
args = ["-c", <<EOF
current_config=$(redis-cli -p ${NOMAD_PORT_db} CONFIG GET save | awk 'NR==2');
if [ -z "$current_config" ]; then
# Enable saving to disk (example: save after 60 seconds if at least 1 key changed)
redis-cli -p ${NOMAD_PORT_db} CONFIG SET save "60 1";
echo "Saving to disk enabled: 60 seconds interval if at least 1 key changed";
else
# Disable saving to disk
redis-cli -p ${NOMAD_PORT_db} CONFIG SET save "";
echo "Saving to disk disabled";
fi;
EOF
]
}
위 방식으로 RDB 스냅샷을 활성화하면 애플리케이션 로그의 출력도 수정돼요.
$ nomad action \
-group=cache \
-task=redis \
-job=redis-actions \
toggle-save-to-disk
Nomad는 액션 실행과 디스크 저장 활성화에 대한 확인을 반환해요.
OK
Saving to disk enabled: 60 seconds interval if at least 1 key changed
서버 로그에 접속해 Redis가 스냅샷을 저장하는 줄을 찾아보세요:
Tue Nov 28 01:31:14 UTC 2023: Current DB Size: 12
28 Nov 01:31:17.800 * 2 changes in 60 seconds. Saving...
28 Nov 01:31:17.800 * Background saving started by pid 36652
28 Nov 01:31:17.810 * DB saved on disk
28 Nov 01:31:17.810 * RDB: 0 MB of memory used by copy-on-write
28 Nov 01:31:17.902 * Background saving terminated with success
Tue Nov 28 01:31:17 UTC 2023: Current DB Size: 12
Nomad 액션은 액션이 실행되는 바로 그 태스크의 상태와 동작에 영향을 줄 수 있어요. 이를 염두에 두면 개발자와 플랫폼 팀이 애플리케이션에서 비즈니스 로직과 운영 로직을 분리하는 데 도움이 돼요.
무기한 및 자체 종료 액션 (Indefinite and self-terminating actions)
지금까지의 모든 액션은 자체 종료형이었어요. 완료되어 완료를 알리는 명령을 실행했어요. 하지만 액션은 원하는 태스크의 완료를 기다리며, Nomad API와 웹 UI는 이를 위해 웹소켓을 사용해요.
Redis 인스턴스의 지연 시간을 관찰하기 위해 ctrl + c 같은 신호 인터럽션으로 직접 중지할 때까지 실행되는 액션을 추가해요:
redis-actions.nomad.hcl
# Performs a latency check of the Redis server.
# This action is a non-terminating action, meaning it will run indefinitely until it is stopped.
# Pass a signal interruption (Ctrl-C) to stop the action.
action "health-check" {
command = "/bin/sh"
args = ["-c", "redis-cli -p ${NOMAD_PORT_db} --latency"]
}
작업을 제출하고 다음으로 액션을 실행해요:
$ nomad action \
-group=cache \
-task=redis \
-job=redis-actions \
-t=true \
health-check
출력은 Redis 인스턴스의 최소, 최대, 평균 지연 시간(ms)을 나타내야 해요. 신호를 인터럽트하거나 웹소켓을 닫으면 액션 실행이 종료돼요.

마무리 (Wrap-up)
액션이 포함된(몇 가지 추가 사항이 들어있는) 완전한 Redis 작업은 아래에서 찾을 수 있어요:
redis-actions.nomad.hcl
job "redis-actions" {
group "cache" {
network {
port "db" {}
}
task "redis" {
driver = "docker"
config {
image = "redis:7"
ports = ["db"]
command = "/bin/sh"
args = ["-c", "redis-server --port ${NOMAD_PORT_db} & /local/db_log.sh"]
}
template {
data = <<EOF
#!/bin/sh
while true; do
echo "$(date): Current DB Size: $(redis-cli -p ${NOMAD_PORT_db} DBSIZE)"
sleep 3
done
EOF
destination = "local/db_log.sh"
perms = "0755"
}
resources {
cpu = 128
memory = 128
}
service {
name = "redis-service"
port = "db"
provider = "nomad"
check {
name = "alive"
type = "tcp"
port = "db"
interval = "10s"
timeout = "2s"
}
}
# Adds a random key/value to the Redis database
action "add-random-key" {
command = "/bin/sh"
args = ["-c", "key=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13); value=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13); redis-cli -p ${NOMAD_PORT_db} SET $key $value; echo Key $key added with value $value"]
}
# Adds a random key/value with a "temp_" prefix to the Redis database
action "add-random-temporary-key" {
command = "/bin/sh"
args = ["-c", "key=temp_$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13); value=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13); redis-cli -p ${NOMAD_PORT_db} SET $key $value; echo Key $key added with value $value"]
}
# Lists all keys currently stored in the Redis database.
action "list-keys" {
command = "/bin/sh"
args = ["-c", "redis-cli -p ${NOMAD_PORT_db} KEYS '*"]
}
# Performs a latency check of the Redis server.
# This action is a non-terminating action, meaning it will run indefinitely until it is stopped.
# Pass a signal interruption (Ctrl-C) to stop the action.
action "health-check" {
command = "/bin/sh"
args = ["-c", "redis-cli -p ${NOMAD_PORT_db} --latency"]
}
# Deletes all keys with a 'temp_' prefix
action "flush-temp-keys" {
command = "/bin/sh"
args = ["-c", <<EOF
keys_to_delete=$(redis-cli -p ${NOMAD_PORT_db} --scan --pattern 'temp_*')
if [ -n "$keys_to_delete" ]; then
# Count the number of keys to delete
deleted_count=$(echo "$keys_to_delete" | wc -l)
# Execute the delete command
echo "$keys_to_delete" | xargs redis-cli -p ${NOMAD_PORT_db} DEL
else
deleted_count=0
fi
remaining_keys=$(redis-cli -p ${NOMAD_PORT_db} DBSIZE)
echo "$deleted_count temporary keys removed; $remaining_keys keys remaining in database"
EOF
]
}
# Toggles saving to disk (RDB persistence). When enabled, allocation logs will indicate a save every 60 seconds.
action "toggle-save-to-disk" {
command = "/bin/sh"
args = ["-c", <<EOF
current_config=$(redis-cli -p ${NOMAD_PORT_db} CONFIG GET save | awk 'NR==2');
if [ -z "$current_config" ]; then
# Enable saving to disk (example: save after 60 seconds if at least 1 key changed)
redis-cli -p ${NOMAD_PORT_db} CONFIG SET save "60 1";
echo "Saving to disk enabled: 60 seconds interval if at least 1 key changed";
else
# Disable saving to disk
redis-cli -p ${NOMAD_PORT_db} CONFIG SET save "";
echo "Saving to disk disabled";
fi;
EOF
]
}
}
}
}
이 액션들을 복제하고 수정해 Nomad에서 액션 기반 워크플로의 잠재력을 탐색해 보세요.
워크플로에서 액션을 어떻게 활용할 수 있는지 더 탐색하려면 다음을 고려해 보세요:
- 위 예제들은 대부분 단일 태스크 그룹과 태스크가 있는 작업의 단일 할당에서 격리되어 실행되는 자체 포함형이에요. 여러 그룹과 태스크를 가진 작업을 만들고, 그 액션들이 서비스 디스커버리를 통해 서로 통신할 수 있게 해보세요.
- GET job actions 엔드포인트를 사용해 작업과 그 그룹 및 태스크에서 사용할 수 있는 액션 목록을 확인해 보세요.
- Nomad 환경 변수를 활용하는 액션을 작성해 보세요. 예를 들어 다음 액션들은 운영자가 시스템 상태를 파악하기 위한 바로가기를 Nomad 작업에 추가하는 방법을 보여줘요:
action "get-alloc-info" {
command = "/bin/sh"
args = ["-c",
<<EOT
nomad alloc status ${NOMAD_ALLOC_ID}
EOT
]
}
action "get-event-stream" {
command = "/usr/bin/curl"
args = ["-s", "localhost:4646/v1/event/stream", " | ", "jq"]
}
정리 (Clean up)
Nomad 액션 작업을 중지하려면 Nomad CLI를 사용해요:
$ nomad job stop redis-actions
작성한 jobspec은 파일시스템에 남지만, 작업 실행에 사용된 리소스는 해제되며 Nomad는 잠시 후 중지된 작업을 자동으로 가비지 컬렉션해요.