컨테이너&가상화
CRI-O기반의 k8s설치
잘나가는전산쟁이
2022. 7. 22. 02:27
728x90
반응형
SMALL
사전사항
- OS환경설정
1
$> swapoff -a
2
3$> cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
4br_netfilter
5EOF
6
7$> cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
8net.bridge.bridge-nf-call-ip6tables = 1
9net.bridge.bridge-nf-call-iptables = 1
10EOF
11
12$> sudo sysctl --system
- crio / kubernetees 패키지 리포지터리 구성
1
$> cat /etc/yum.repos.d/libcontainers.repo
2[devel_kubic_libcontainers_stable]
3name=Stable Releases of Upstream github.com/containers packages (CentOS_8)
4type=rpm-md
5baseurl=https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/CentOS_8/
6gpgcheck=1
7gpgkey=https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/CentOS_8/repodata/repomd.xml.key
8enabled=1
1$> cat /etc/yum.repos.d/cri-o-1.23.repo
2[devel_kubic_libcontainers_stable_cri-o_1.23]
3name=devel:kubic:libcontainers:stable:cri-o:1.23 (CentOS_8)
4type=rpm-md
5baseurl=https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/1.23/CentOS_8/
6gpgcheck=1
7gpgkey=https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/1.23/CentOS_8/repodata/repomd.xml.key
8enabled=1
1$> cat /etc/yum.repos.d/kubernetes.repo
2[kubernetes]
3name=Kubernetes
4baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-$basearch
5enabled=1
6gpgcheck=1
7repo_gpgcheck=1
8gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
- 패키지 설치
1
$> yum install kubelet kubeadm kubectl libcgroup cri-o cri-tools -y
2$> systemctl enable crio --now
3$> systemctl enable kubelet
클러스터 생성 (control palin 1번에서만 수행)
- kubeadm 클러스터 생성
1
$> kubeadm init --control-plane-endpoint 172.21.107.238:6443 --pod-network-cidr 10.250.0.0/16 --ignore-preflight-errors=all --upload-certs
2
3## 결과값중에 control / worker 노드별 join 명령이 다르기 때문에 별도로 복사해놓어야 함
##Control node용1$> kubeadm join 172.21.107.238:6443 --token abcd \
2--discovery-token-ca-cert-hash sha256:yyy \
3--control-plane --certificate-key zzz
## Worker Node용1$> kubeadm join 172.21.107.238:6443 --token abcd \
2--discovery-token-ca-cert-hash sha256:yyyy \
3--ignore-preflight-errors=all
- 인증서 정보 복사
1
$> mkdir -p $HOME/.kube
2$> /bin/cp /etc/kubernetes/admin.conf $HOME/.kube/config
3$> chown $(id -u):$(id -g) $HOME/.kube/config
4$> export KUBECONFIG=/etc/kubernetes/admin.conf
- CNI 설치(Calico)
1
$> curl https://projectcalico.docs.tigera.io/manifests/calico.yaml -O
2$> kubectl apply -f calico.yaml
클러스터 연동
- 타 Control plain 연동 (Control Plain 한대씩 순차 작업 수행)
1
$> kubeadm join 172.21.107.238:6443 --token abcd \
2--discovery-token-ca-cert-hash sha256:yyy \
3--control-plane --certificate-key zzz
- 노드 연동 확인 (Control plain에서 수행)
1
$> kubectl get no
2NAME STATUS ROLES AGE VERSION
3k8stesttx-k8s-master-dev01 Ready control-plane,master 3h6m v1.23.5
4k8stesttx-k8s-master-dev02 Ready control-plane,master 3h6m v1.23.5
5k8stesttx-k8s-master-dev03 Ready control-plane,master 3h6m v1.23.5
- Worker Node 연동
1
$> kubeadm join 172.21.107.238:6443 --token abcd \
2--discovery-token-ca-cert-hash sha256:yyyy \
3--ignore-preflight-errors=all
- 노드 연동 확인 (Control plain에서 수행)
1
$> kubectl get no
2NAME STATUS ROLES AGE VERSION
3...
4k8stesttx-k8s-worker-dev01 Ready <none> 40m v1.23.5
5k8stesttx-k8s-worker-dev02 Ready <none> 40m v1.23.5
6k8stesttx-k8s-worker-dev03 Ready <none> 40m v1.23.5
k8s 인증서 10년으로 연장
- 인증서 연장 스크립트
1
#!/usr/bin/env bash
2
3set -o errexit
4set -o pipefail
5# set -o xtrace
6
7# set output color
8NC='\033[0m'
9RED='\033[31m'
10GREEN='\033[32m'
11YELLOW='\033[33m'
12BLUE='\033[34m'
13
14log::err() {
15printf "[$(date +'%Y-%m-%dT%H:%M:%S.%2N%z')][${RED}ERROR${NC}] %b\n" "$@"
16}
17
18log::info() {
19printf "[$(date +'%Y-%m-%dT%H:%M:%S.%2N%z')][INFO] %b\n" "$@"
20}
21
22log::warning() {
23printf "[$(date +'%Y-%m-%dT%H:%M:%S.%2N%z')][${YELLOW}WARNING${NC}] \033[0m%b\n" "$@"
24}
25
26check_file() {
27if [[ ! -r ${1} ]]; then
28log::err "can not find ${1}"
29exit 1
30fi
31}
32
33# get x509v3 subject alternative name from the old certificate
34cert::get_subject_alt_name() {
35local cert=${1}.crt
36local alt_name
37
38check_file "${cert}"
39alt_name=$(openssl x509 -text -noout -in "${cert}" | grep -A1 'Alternative' | tail -n1 | sed 's/[[:space:]]*Address//g')
40printf "%s\n" "${alt_name}"
41}
42
43# get subject from the old certificate
44cert::get_subj() {
45local cert=${1}.crt
46local subj
47
48check_file "${cert}"
49subj=$(openssl x509 -text -noout -in "${cert}" | grep "Subject:" | sed 's/Subject:/\//g;s/\,/\//;s/[[:space:]]//g')
50printf "%s\n" "${subj}"
51}
52
53cert::backup_file() {
54local file=${1}
55if [[ ! -e ${file}.old-$(date +%Y%m%d) ]]; then
56cp -rp "${file}" "${file}.old-$(date +%Y%m%d)"
57log::info "backup ${file} to ${file}.old-$(date +%Y%m%d)"
58else
59log::warning "does not backup, ${file}.old-$(date +%Y%m%d) already exists"
60fi
61}
62
63# check certificate expiration
64cert::check_cert_expiration() {
65local cert=${1}.crt
66local cert_expires
67
68cert_expires=$(openssl x509 -text -noout -in "${cert}" | awk -F ": " '/Not After/{print$2}')
69printf "%s\n" "${cert_expires}"
70}
71
72# check kubeconfig expiration
73cert::check_kubeconfig_expiration() {
74local config=${1}.conf
75local cert
76local cert_expires
77
78cert=$(grep "client-certificate-data" "${config}" | awk '{print$2}' | base64 -d)
79cert_expires=$(openssl x509 -text -noout -in <(printf "%s" "${cert}") | awk -F ": " '/Not After/{print$2}')
80printf "%s\n" "${cert_expires}"
81}
82
83# check etcd certificates expiration
84cert::check_etcd_certs_expiration() {
85local cert
86local certs
87
88certs=(
89"${ETCD_CERT_CA}"
90"${ETCD_CERT_SERVER}"
91"${ETCD_CERT_PEER}"
92"${ETCD_CERT_HEALTHCHECK_CLIENT}"
93"${ETCD_CERT_APISERVER_ETCD_CLIENT}"
94)
95
96for cert in "${certs[@]}"; do
97if [[ ! -r ${cert} ]]; then
98printf "%-50s%-30s\n" "${cert}.crt" "$(cert::check_cert_expiration "${cert}")"
99fi
100done
101}
102
103# check master certificates expiration
104cert::check_master_certs_expiration() {
105local certs
106local kubeconfs
107local cert
108local conf
109
110certs=(
111"${CERT_CA}"
112"${CERT_APISERVER}"
113"${CERT_APISERVER_KUBELET_CLIENT}"
114"${FRONT_PROXY_CA}"
115"${FRONT_PROXY_CLIENT}"
116)
117
118kubeconfs=(
119"${CONF_CONTROLLER_MANAGER}"
120"${CONF_SCHEDULER}"
121"${CONF_ADMIN}"
122)
123
124printf "%-50s%-30s\n" "CERTIFICATE" "EXPIRES"
125
126for conf in "${kubeconfs[@]}"; do
127if [[ ! -r ${conf} ]]; then
128printf "%-50s%-30s\n" "${conf}.config" "$(cert::check_kubeconfig_expiration "${conf}")"
129fi
130done
131
132for cert in "${certs[@]}"; do
133if [[ ! -r ${cert} ]]; then
134printf "%-50s%-30s\n" "${cert}.crt" "$(cert::check_cert_expiration "${cert}")"
135fi
136done
137}
138
139# check all certificates expiration
140cert::check_all_expiration() {
141cert::check_master_certs_expiration
142cert::check_etcd_certs_expiration
143}
144
145# generate certificate whit client, server or peer
146# Args:
147# $1 (the name of certificate)
148# $2 (the type of certificate, must be one of client, server, peer)
149# $3 (the subject of certificates)
150# $4 (the validity of certificates) (days)
151# $5 (the name of ca)
152# $6 (the x509v3 subject alternative name of certificate when the type of certificate is server or peer)
153cert::gen_cert() {
154local cert_name=${1}
155local cert_type=${2}
156local subj=${3}
157local cert_days=${4}
158local ca_name=${5}
159local alt_name=${6}
160local ca_cert=${ca_name}.crt
161local ca_key=${ca_name}.key
162local cert=${cert_name}.crt
163local key=${cert_name}.key
164local csr=${cert_name}.csr
165local common_csr_conf='distinguished_name = dn\n[dn]\n[v3_ext]\nkeyUsage = critical, digitalSignature, keyEncipherment\n'
166
167for file in "${ca_cert}" "${ca_key}" "${cert}" "${key}"; do
168check_file "${file}"
169done
170
171case "${cert_type}" in
172client)
173csr_conf=$(printf "%bextendedKeyUsage = clientAuth\n" "${common_csr_conf}")
174;;
175server)
176csr_conf=$(printf "%bextendedKeyUsage = serverAuth\nsubjectAltName = %b\n" "${common_csr_conf}" "${alt_name}")
177;;
178peer)
179csr_conf=$(printf "%bextendedKeyUsage = serverAuth, clientAuth\nsubjectAltName = %b\n" "${common_csr_conf}" "${alt_name}")
180;;
181*)
182log::err "unknow, unsupported certs type: ${YELLOW}${cert_type}${NC}, supported type: client, server, peer"
183exit 1
184;;
185esac
186
187# gen csr
188openssl req -new -key "${key}" -subj "${subj}" -reqexts v3_ext \
189-config <(printf "%b" "${csr_conf}") \
190-out "${csr}" >/dev/null 2>&1
191# gen cert
192openssl x509 -in "${csr}" -req -CA "${ca_cert}" -CAkey "${ca_key}" -CAcreateserial -extensions v3_ext \
193-extfile <(printf "%b" "${csr_conf}") \
194-days "${cert_days}" -out "${cert}" >/dev/null 2>&1
195
196rm -f "${csr}"
197}
198
199cert::update_kubeconf() {
200local cert_name=${1}
201local kubeconf_file=${cert_name}.conf
202local cert=${cert_name}.crt
203local key=${cert_name}.key
204local subj
205local cert_base64
206
207check_file "${kubeconf_file}"
208# get the key from the old kubeconf
209grep "client-key-data" "${kubeconf_file}" | awk '{print$2}' | base64 -d >"${key}"
210# get the old certificate from the old kubeconf
211grep "client-certificate-data" "${kubeconf_file}" | awk '{print$2}' | base64 -d >"${cert}"
212# get subject from the old certificate
213subj=$(cert::get_subj "${cert_name}")
214cert::gen_cert "${cert_name}" "client" "${subj}" "${CERT_DAYS}" "${CERT_CA}"
215# get certificate base64 code
216cert_base64=$(base64 -w 0 "${cert}")
217
218# set certificate base64 code to kubeconf
219sed -i 's/client-certificate-data:.*/client-certificate-data: '"${cert_base64}"'/g' "${kubeconf_file}"
220
221rm -f "${cert}"
222rm -f "${key}"
223}
224
225cert::update_etcd_cert() {
226local subj
227local subject_alt_name
228local cert
229
230# generate etcd server,peer certificate
231# /etc/kubernetes/pki/etcd/server
232# /etc/kubernetes/pki/etcd/peer
233for cert in ${ETCD_CERT_SERVER} ${ETCD_CERT_PEER}; do
234subj=$(cert::get_subj "${cert}")
235subject_alt_name=$(cert::get_subject_alt_name "${cert}")
236cert::gen_cert "${cert}" "peer" "${subj}" "${CERT_DAYS}" "${ETCD_CERT_CA}" "${subject_alt_name}"
237log::info "${GREEN}updated ${BLUE}${cert}.conf${NC}"
238done
239
240# generate etcd healthcheck-client,apiserver-etcd-client certificate
241# /etc/kubernetes/pki/etcd/healthcheck-client
242# /etc/kubernetes/pki/apiserver-etcd-client
243for cert in ${ETCD_CERT_HEALTHCHECK_CLIENT} ${ETCD_CERT_APISERVER_ETCD_CLIENT}; do
244subj=$(cert::get_subj "${cert}")
245cert::gen_cert "${cert}" "client" "${subj}" "${CERT_DAYS}" "${ETCD_CERT_CA}"
246log::info "${GREEN}updated ${BLUE}${cert}.conf${NC}"
247done
248
249# restart etcd
250docker ps | awk '/k8s_etcd/{print$1}' | xargs -r -I '{}' docker restart {} >/dev/null 2>&1 || true
251log::info "restarted etcd"
252}
253
254cert::update_master_cert() {
255local subj
256local subject_alt_name
257local conf
258
259# generate apiserver server certificate
260# /etc/kubernetes/pki/apiserver
261subj=$(cert::get_subj "${CERT_APISERVER}")
262subject_alt_name=$(cert::get_subject_alt_name "${CERT_APISERVER}")
263cert::gen_cert "${CERT_APISERVER}" "server" "${subj}" "${CERT_DAYS}" "${CERT_CA}" "${subject_alt_name}"
264log::info "${GREEN}updated ${BLUE}${CERT_APISERVER}.crt${NC}"
265
266# generate apiserver-kubelet-client certificate
267# /etc/kubernetes/pki/apiserver-kubelet-client
268subj=$(cert::get_subj "${CERT_APISERVER_KUBELET_CLIENT}")
269cert::gen_cert "${CERT_APISERVER_KUBELET_CLIENT}" "client" "${subj}" "${CERT_DAYS}" "${CERT_CA}"
270log::info "${GREEN}updated ${BLUE}${CERT_APISERVER_KUBELET_CLIENT}.crt${NC}"
271
272# generate kubeconf for controller-manager,scheduler and kubelet
273# /etc/kubernetes/controller-manager,scheduler,admin,kubelet.conf
274for conf in ${CONF_CONTROLLER_MANAGER} ${CONF_SCHEDULER} ${CONF_ADMIN} ${CONF_KUBELET}; do
275if [[ ${conf##*/} == "kubelet" ]]; then
276# https://github.com/kubernetes/kubeadm/issues/1753
277set +e
278grep kubelet-client-current.pem /etc/kubernetes/kubelet.conf >/dev/null 2>&1
279kubelet_cert_auto_update=$?
280set -e
281if [[ "$kubelet_cert_auto_update" == "0" ]]; then
282log::info "does not need to update kubelet.conf"
283continue
284fi
285fi
286
287# update kubeconf
288cert::update_kubeconf "${conf}"
289log::info "${GREEN}updated ${BLUE}${conf}.conf${NC}"
290
291# copy admin.conf to ${HOME}/.kube/config
292if [[ ${conf##*/} == "admin" ]]; then
293mkdir -p "${HOME}/.kube"
294local config=${HOME}/.kube/config
295local config_backup
296config_backup=${HOME}/.kube/config.old-$(date +%Y%m%d)
297if [[ -f ${config} ]] && [[ ! -f ${config_backup} ]]; then
298cp -fp "${config}" "${config_backup}"
299log::info "backup ${config} to ${config_backup}"
300fi
301cp -fp "${conf}.conf" "${HOME}/.kube/config"
302log::info "copy the admin.conf to ${HOME}/.kube/config"
303fi
304done
305
306# generate front-proxy-client certificate
307# /etc/kubernetes/pki/front-proxy-client
308subj=$(cert::get_subj "${FRONT_PROXY_CLIENT}")
309cert::gen_cert "${FRONT_PROXY_CLIENT}" "client" "${subj}" "${CERT_DAYS}" "${FRONT_PROXY_CA}"
310log::info "${GREEN}updated ${BLUE}${FRONT_PROXY_CLIENT}.crt${NC}"
311
312# restart apiserver, controller-manager, scheduler and kubelet
313for item in "apiserver" "controller-manager" "scheduler"; do
314docker ps | awk '/k8s_kube-'${item}'/{print$1}' | xargs -r -I '{}' docker restart {} >/dev/null 2>&1 || true
315log::info "restarted ${item}"
316done
317systemctl restart kubelet || true
318log::info "restarted kubelet"
319}
320
321main() {
322local node_type=$1
323
324CERT_DAYS=3650
325
326KUBE_PATH=/etc/kubernetes
327PKI_PATH=${KUBE_PATH}/pki
328
329# master certificates path
330# apiserver
331CERT_CA=${PKI_PATH}/ca
332CERT_APISERVER=${PKI_PATH}/apiserver
333CERT_APISERVER_KUBELET_CLIENT=${PKI_PATH}/apiserver-kubelet-client
334CONF_CONTROLLER_MANAGER=${KUBE_PATH}/controller-manager
335CONF_SCHEDULER=${KUBE_PATH}/scheduler
336CONF_ADMIN=${KUBE_PATH}/admin
337CONF_KUBELET=${KUBE_PATH}/kubelet
338# front-proxy
339FRONT_PROXY_CA=${PKI_PATH}/front-proxy-ca
340FRONT_PROXY_CLIENT=${PKI_PATH}/front-proxy-client
341
342# etcd certificates path
343ETCD_CERT_CA=${PKI_PATH}/etcd/ca
344ETCD_CERT_SERVER=${PKI_PATH}/etcd/server
345ETCD_CERT_PEER=${PKI_PATH}/etcd/peer
346ETCD_CERT_HEALTHCHECK_CLIENT=${PKI_PATH}/etcd/healthcheck-client
347ETCD_CERT_APISERVER_ETCD_CLIENT=${PKI_PATH}/apiserver-etcd-client
348
349case ${node_type} in
350# etcd)
351# # update etcd certificates
352# cert::update_etcd_cert
353# ;;
354master)
355# check certificates expiration
356cert::check_master_certs_expiration
357# backup $KUBE_PATH to $KUBE_PATH.old-$(date +%Y%m%d)
358cert::backup_file "${KUBE_PATH}"
359# update master certificates and kubeconf
360log::info "${GREEN}updating...${NC}"
361cert::update_master_cert
362log::info "${GREEN}done!!!${NC}"
363# check certificates expiration after certificates updated
364cert::check_master_certs_expiration
365;;
366all)
367# check certificates expiration
368cert::check_all_expiration
369# backup $KUBE_PATH to $KUBE_PATH.old-$(date +%Y%m%d)
370cert::backup_file "${KUBE_PATH}"
371# update etcd certificates
372log::info "${GREEN}updating...${NC}"
373cert::update_etcd_cert
374# update master certificates and kubeconf
375cert::update_master_cert
376log::info "${GREEN}done!!!${NC}"
377# check certificates expiration after certificates updated
378cert::check_all_expiration
379;;
380check)
381# check certificates expiration
382cert::check_all_expiration
383;;
384*)
385log::err "unknown, unsupported cert type: ${node_type}, supported type: \"all\", \"master\""
386printf "Documentation: https://github.com/yuyicai/update-kube-cert
387example:
388'\033[32m./update-kubeadm-cert.sh all\033[0m' update all etcd certificates, master certificates and kubeconf
389/etc/kubernetes
390├── admin.conf
391├── controller-manager.conf
392├── scheduler.conf
393├── kubelet.conf
394└── pki
395├── apiserver.crt
396├── apiserver-etcd-client.crt
397├── apiserver-kubelet-client.crt
398├── front-proxy-client.crt
399└── etcd
400├── healthcheck-client.crt
401├── peer.crt
402└── server.crt
403
404'\033[32m./update-kubeadm-cert.sh master\033[0m' update only master certificates and kubeconf
405/etc/kubernetes
406├── admin.conf
407├── controller-manager.conf
408├── scheduler.conf
409├── kubelet.conf
410└── pki
411├── apiserver.crt
412├── apiserver-kubelet-client.crt
413└── front-proxy-client.crt
414"
415exit 1
416;;
417esac
418}
419
420main "$@"
- 업데이트 전 인증서 정보 확인
1
$> kubeadm certs check-expiration
2[check-expiration] Reading configuration from the cluster...
3[check-expiration] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
4
5CERTIFICATE EXPIRES RESIDUAL TIME CERTIFICATE AUTHORITY EXTERNALLY MANAGED
6admin.conf Apr 17, 2023 06:09 UTC 364d ca no
7apiserver Apr 17, 2023 06:09 UTC 364d ca no
8apiserver-etcd-client Apr 17, 2023 06:09 UTC 364d etcd-ca no
9apiserver-kubelet-client Apr 17, 2023 06:09 UTC 364d ca no
10controller-manager.conf Apr 17, 2023 06:09 UTC 364d ca no
11etcd-healthcheck-client Apr 17, 2023 06:09 UTC 364d etcd-ca no
12etcd-peer Apr 17, 2023 06:09 UTC 364d etcd-ca no
13etcd-server Apr 17, 2023 06:09 UTC 364d etcd-ca no
14front-proxy-client Apr 17, 2023 06:09 UTC 364d front-proxy-ca no
15scheduler.conf Apr 17, 2023 06:09 UTC 364d ca no
16
17CERTIFICATE AUTHORITY EXPIRES RESIDUAL TIME EXTERNALLY MANAGED
18ca Apr 17, 2032 04:17 UTC 9y no
19etcd-ca Apr 17, 2032 04:17 UTC 9y no
20front-proxy-ca Apr 17, 2032 04:17 UTC 9y no
- 인증서 업데이트 (Control Plain 1대씩 순차 작업 수행, 서버단위로 30초 가량 대기 필요)
1
$> chmod +x cert_update.sh
2$> ./cert_update.sh
3...
- 인증서 갱신정보 확인
1
$> kubeadm certs check-expiration
2[check-expiration] Reading configuration from the cluster...
3[check-expiration] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
4
5CERTIFICATE EXPIRES RESIDUAL TIME CERTIFICATE AUTHORITY EXTERNALLY MANAGED
6admin.conf Apr 17, 2032 06:09 UTC 9y ca no
7apiserver Apr 17, 2032 06:09 UTC 9y ca no
8apiserver-etcd-client Apr 17, 2032 06:09 UTC 9y etcd-ca no
9apiserver-kubelet-client Apr 17, 2032 06:09 UTC 9y ca no
10controller-manager.conf Apr 17, 2032 06:09 UTC 9y ca no
11etcd-healthcheck-client Apr 17, 2032 06:09 UTC 9y etcd-ca no
12etcd-peer Apr 17, 2032 06:09 UTC 9y etcd-ca no
13etcd-server Apr 17, 2032 06:09 UTC 9y etcd-ca no
14front-proxy-client Apr 17, 2032 06:09 UTC 9y front-proxy-ca no
15scheduler.conf Apr 17, 2032 06:09 UTC 9y ca no
16
17CERTIFICATE AUTHORITY EXPIRES RESIDUAL TIME EXTERNALLY MANAGED
18ca Apr 17, 2032 04:17 UTC 9y no
19etcd-ca Apr 17, 2032 04:17 UTC 9y no
20front-proxy-ca Apr 17, 2032 04:17 UTC 9y no
Reference
728x90
반응형
LIST