kubernetes를 사용하다보면 수많은 yaml 파일들을 사용하고 때로는 만들기도 한다. kubernetes는 내부의 자원들을 yaml로 기술하기 때문이다. kubernetes 를 활용한 컨테이너 플랫폼을 만들고 있기에 이에 대해 접하는 경우가 많고 또한 자동화를 위해서는 상황에 맞는 자원을 yaml 로 작성하고 수정하고 보관하는 일들을 많이 수행하고 있다. 환경에 맞는 값을 지정하기 위해서는 yaml 구조에서 정확한 위치의 값을 변경하는 것은 필수적인 일이다. 이를 위해 이전에는 python의 yaml parser를 활용하는 코드를 부분적으로 배치해서 사용했었다. bash 쉘스크립트를 활용하는 것이 효율적인 코드에서 몇몇 부분의 값을 치환하기 위해 값을 변경하기위해 python 코드를 추가하는 것은 전체적으로 많은 코드를 생성하고 이에 대한 관리나 재활용 측면에서 불리한 측면이 있었다. 이에따라 bash에서 해결할 수 있는 방법을 고민하였고 알게된 방법을 이글을 통해 공유하고자 한다.
먼저 jq에 대해 알아보면, bash에서 rest api를 호출하는 경우 반환값이 json 형태로 얻어지는 경우가 많은데 전체 반환값에서 필요한 값을 파싱하기 위해 나온 명령이다. grep 명령이나 awk 스크립트를 사용하여 해결할 수 있으나 매우 복잡하다. 이때 jq을 활용하면 json 데이터에서 ‘.’ 구분자를 사용하여 구조안에서 원하는 값을 간편하게 다룰수 있게 해준다. jq를 직접 활용하지 않더라도 해당 기능을 플러그인 형태로 제공하는 경우가 많은데 kubectl의 경우 jsonpath를 통해 원하는 값을 가져오는 것이 그것이다. 아래 예는 kubectl의 jsonpath를 사용하여 서비스에서 생성된 로드밸런서의 주소를 가져오는 것이다.
siim@adm:~$ kubectl get svc -n nexus dev-nexus-repository-manager -o yaml
apiVersion: v1
kind: Service
metadata:
annotations:
meta.helm.sh/release-name: dev-nexus-repository-manager
meta.helm.sh/release-namespace: nexus
creationTimestamp: "2023-01-09T07:51:58Z"
finalizers:
- service.kubernetes.io/load-balancer-cleanup
labels:
app.kubernetes.io/instance: dev-nexus-repository-manager
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: nexus-repository-manager
app.kubernetes.io/version: 3.45.0
helm.sh/chart: nexus-repository-manager-45.0.0
name: dev-nexus-repository-manager
namespace: nexus
resourceVersion: "16122549"
uid: 650ce058-f73a-4ed2-87e1-b8719d775306
spec:
allocateLoadBalancerNodePorts: true
clusterIP: 10.106.112.16
clusterIPs:
- 10.106.112.16
externalTrafficPolicy: Cluster
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- name: nexus-ui
nodePort: 32270
port: 8081
protocol: TCP
targetPort: 8081
selector:
app.kubernetes.io/instance: dev-nexus-repository-manager
app.kubernetes.io/name: nexus-repository-manager
sessionAffinity: None
type: LoadBalancer
status:
loadBalancer:
ingress:
- hostname: a650ce058f73a4ed287e1b8719d77530-11111111111.ap-northeast-2.elb.amazonaws.com
siim@adm:~$ kubectl get svc -n nexus dev-nexus-repository-manager -o jsonpath='{.status.loadBalancer.ingress[0].hostname}'
a650ce058f73a4ed287e1b8719d77530-11111111111.ap-northeast-2.elb.amazonaws.com
사람이 판독할 수 있고 기계가 활용할 수 있는 포맷으로 json이 먼저 보편화 되었고 몇몇 활용 사례도 있었으나 kubernetes가 확산되면서 yaml 포맷도 활용되기 시작하였다. 이에따라 jq와 동일한 기능을 yaml에 대해서도 수행하는 yq라는 유틸리티도 등장하였다. kubernetes를 활용하는 TACO를 개발하던 초기에는 사용할 수 없었던 수준의 유틸리티가 현재는 jq에 기능을 거의 따라잡는 수준까지 발전하였다. (현재까지도 mikefarah 라는 개인계정에서 개발 및 배포가 진행중 이다.)
맥의 경우 brew등 패키지 관리자를 통해 설치할 수 도 있으나 일반적으로는 git에서 배포본을 받아서 사용한다.
siim@adm:~$ wget <https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY}.tar.gz> -O - |\
tar xz && mv ${BINARY} /usr/bin/yq
siim@adm:~$ yq
Usage:
yq [flags]
yq [command]
Examples:
# yq defaults to 'eval' command if no command is specified. See "yq eval --help" for more examples.
# read the "stuff" node from "myfile.yml"
yq '.stuff' < myfile.yml
# update myfile.yml in place
yq -i '.stuff = "foo"' myfile.yml
# print contents of sample.json as idiomatic YAML
yq -P sample.json
Available Commands:
completion Generate the autocompletion script for the specified shell
eval (default) Apply the expression to each document in each yaml file in sequence
eval-all Loads _all_ yaml documents of _all_ yaml files and runs expression once
help Help about any command
shell-completion Generate completion script
Flags:
-C, --colors force print with colors
-e, --exit-status set exit status if there are no matches or null or false is returned
--expression string forcibly set the expression argument. Useful when yq argument detection thinks your expression is a file.
--from-file string Load expression from specified file.
-f, --front-matter string (extract|process) first input as yaml front-matter. Extract will pull out the yaml content, process will run the expression against the yaml content, leaving the remaining data intact
--header-preprocess Slurp any header comments and separators before processing expression. (default true)
-h, --help help for yq
-I, --indent int sets indent level for output (default 2)
-i, --inplace update the file inplace of first file given.
-p, --input-format string [auto|a|yaml|y|props|p|xml|x|tsv|t|csv|c|toml] parse format for input. Note that json is a subset of yaml. (default "auto")
-M, --no-colors force print with no colors
-N, --no-doc Don't print document separators (---)
-0, --nul-output Use NUL char to separate values. If unwrap scalar is also set, fail if unwrapped scalar contains NUL char.
-n, --null-input Don't read input, simply evaluate the expression given. Useful for creating docs from scratch.
-o, --output-format string [auto|a|yaml|y|json|j|props|p|xml|x|tsv|t|csv|c] output format type. (default "auto")
-P, --prettyPrint pretty print, shorthand for '... style = ""'
-s, --split-exp string print each result (or doc) into a file named (exp). [exp] argument must return a string. You can use $index in the expression as the result counter.
--split-exp-file string Use a file to specify the split-exp expression.
-r, --unwrapScalar unwrap scalar, print the value with no quotes, colors or comments. Defaults to true for yaml (default true)
-v, --verbose verbose mode
-V, --version Print version information and quit
--xml-attribute-prefix string prefix for xml attributes (default "+@")
--xml-content-name string name for xml content (if no attribute name is present). (default "+content")
--xml-directive-name string name for xml directives (e.g. <!DOCTYPE thing cat>) (default "+directive")
--xml-keep-namespace enables keeping namespace after parsing attributes (default true)
--xml-proc-inst-prefix string prefix for xml processing instructions (e.g. <?xml version="1"?>) (default "+p_")
--xml-raw-token enables using RawToken method instead Token. Commonly disables namespace translations. See <https://pkg.go.dev/encoding/xml#Decoder.RawToken> for details. (default true)
--xml-skip-directives skip over directives (e.g. <!DOCTYPE thing cat>)
--xml-skip-proc-inst skip over process instructions (e.g. <?xml version="1"?>)
--xml-strict-mode enables strict parsing of XML. See <https://pkg.go.dev/encoding/xml> for more details.
Use "yq [command] --help" for more information about a command.
‘.’을 활용하여 구조 내부 특정값을 가져올수 있으며 배열이 존재하는 경우[]를 통해 순번을 지정할 수 있다.
yq '.a.b[0].c' file.yaml
‘-i’ 옵션을 사용하면 해당위치의 값을 변경할 수 있다.
yq -i '.a.b[0].c = "cool"' file.yaml
strenv 명령을 사용하면 환경변수를 사용할 수 있다.
NAME=mike
yq -i '.a.b[0].c = strenv(NAME)' file.yaml
특정 파일들을 병합할 수 있다. 다음의 경우 해당 디렉토리의 yaml 파일들을 파일명을 기준으로 다음 depth로 넣은 하나의 yaml 결과물을 만든다.
yq ea '. as $item ireduce ({}; . * $item )' path/to/*.yml
그 외 다양한 적용 아이디어들이 공유되고 있으며 이를 기반으로 신규기능이 추가되기도 한다.