8/09/2020

systemd 의 logrotate 분석 과 Timer 사용법

1. systemd의 logrotate 기본구성 과 분석

syslog가 log 파일을 생성을 한다고 하면, 매번 Log를 주기적으로 backup을 하여 보관의 필요성을 느낄 것이며, 이를 위해서 사용되어지는 program이 logrotate이다 

만약 기본설치가 되어있다면, /etc/logrotate.conf 설정이 존재할 것이다. 
다만 logrotate가 자동으로 실행되는 program이 아니며, Linux Filesystem에 따라 sysvInit 일 경우는  cron 을 이용하여 주기적으로 호출되고, 
systemd일 경우는 timer를 이용하여 이를 주기적으로 호출되어 동작가능하다 


  • syslog 관련내용 
logrotate를 알기전에 syslogd에 대해 알자.
  https://ahyuo79.blogspot.com/2019/04/syslog.html


  • 기본설정파일 
  1. /etc/logrotate.conf  : 
  2. /etc/logrotate.d/btmp : 기본으로 설치됨 
  3. /etc/logrotate.d/wtmp


  • systemd의 timer 사용시 분석할 파일 
  1. /lib/systemd/system/logrotate.timer
  2. /lib/systemd/system/logrotate.service
  3. /etc/systemd/system/timers.target.wants/logrotate.timer  (Timer가 enable일 경우 존재)



1.1 logrotate 의 timer 와 service 분석 


  • logrotate 의 service 와 timer 분석

반드시 logrotate.service 와 logrotate.timer 의 prefix name이 logrotate.는 이름으로 동일해야 제대로 동작한다
logrotate.service는  logrotate.timer가 주기적으로 호출되어 를 구동하는 구조로 동작되어있기 때문에, systemd의 timer 현재 systemctl enable이 된 상태이다.


  • logrotate.timer 구조 파악  
분석으로 보면 매일 밤 12시, 즉 0시에 동작이 되며, Timer를 확인하는 주기는 12시간으로 정확성을 많이 떨어트리지만, Persistent를 이용

$ cat /lib/systemd/system/logrotate.timer 
[Unit]
Description=Daily rotation of log files
Documentation=man:logrotate(8) man:logrotate.conf(5)

[Timer]
OnCalendar=daily
AccuracySec=12h
Persistent=true

[Install]
WantedBy=timers.target
// systemctl enable logrotate.timer 할 경우  아래 link 생성됨
// systemctl disable logrotate.timer 할 경우 아래 link 제거됨

// timers.target.wants에 timer가 존재하는 것은 현재 enable 되는 상태
$ ls -al /etc/systemd/system/timers.target.wants/logrotate.timer
/etc/systemd/system/timers.target.wants/logrotate.timer -> /lib/systemd/system/logrotate.timerrotate.timer
$ systemctl status logrotate.timer // 매일 0시에 Trigger되어 동작함 $ systemctl is-enabled logrotate.timer // logrotate.timer enable enabled

systemd.timer 의 OnCalendar 설정정보 시간설정방법
onCalendar Time 설정을 보면 전부 0시기준으로 동작 
daily → *-*-* 00:00:00

시간동기화문제
OnCalendar 일반적으로 사용할 경우 time-sync.target 이후에 설정하도록 의존성을 넣는다고한다. 
  https://www.freedesktop.org/software/systemd/man/systemd.timer.html#Default%20Dependencies


기타 다른예제 (좋은예제)


  • 상위 timer에 의해 실행되는 logrotate.service 
기존의 Service 구조와 다르게 [Install] Section 이 없으며, *wants 및 에 위치 하지도 않는다
현재 systemctl disable logrotate.service 인 상태이지만 상위 timer가 구동을 시켜준다.

$ cat /lib/systemd/system/logrotate.service
[Unit]
Description=Rotate log files
Documentation=man:logrotate(8) man:logrotate.conf(5)
RequiresMountsFor=/var/log
ConditionACPower=true

[Service]
Type=oneshot
ExecStart=/usr/sbin/logrotate /etc/logrotate.conf

# performance options
Nice=19
IOSchedulingClass=best-effort
IOSchedulingPriority=7

# hardening options
#  details: https://www.freedesktop.org/software/systemd/man/systemd.exec.html
#  no ProtectHome for userdir logs
#  no PrivateNetwork for mail deliviery
#  no ProtectKernelTunables for working SELinux with systemd older than 235
MemoryDenyWriteExecute=true
PrivateDevices=true
PrivateTmp=true
ProtectControlGroups=true
ProtectKernelModules=true
ProtectSystem=full
RestrictRealtime=true

$ systemctl status logrotate.service // logrotate.service 동작시간 확인 

$ systemctl is-enabled logrotate.service // logrotate.timer에 의해 동작 
static

1.2 logrotate 설정분석 


기본적으로 설정은 /etc/logrotate.conf에서 하지만, 확장설정이 가능하므로, 
아래와 같이 include를 사용하여 /etc/logrotate.d 안에 있는 존재하는 모든 파일도 허용한다

  • 기본설정구성 
$ cat /etc/logrotate.conf
# see "man logrotate" for details
# rotate log files weekly
weekly

# keep 4 weeks worth of backlogs
rotate 4

# create new (empty) log files after rotating old ones
create

# use date as a suffix of the rotated file
dateext

# uncomment this if you want your log files compressed
#compress

# packages drop log rotation information into this directory
include /etc/logrotate.d

# system-specific logs may be also be configured here.

mail command가 지원되며 e-mail에 log 정보도 같이 수신받을 수 있다. 

  • /var/log/btmp 설정 
실패한 Login 정보 (lastb 명령어로 확인)
$ cat /etc/logrotate.d/btmp
# no packages own btmp -- we'll rotate it here
/var/log/btmp {
    missingok
    monthly
    create 0600 root utmp
    rotate 1
}

  • /var/log/wtmp 설정 
사용자의 Login/out과 시스템정보를 관리 (last 명령어로 확인)
$ cat /etc/logrotate.d/wtmp
# no packages own wtmp -- we'll rotate it here
/var/log/wtmp {
    missingok
    monthly
    create 0664 root utmp
    minsize 1M                # 상위 monthly 와 별개로 사이즈가 넘으면 rotate 
    rotate 1
}

last 와 lastb

  • /var/log/message 와 /var/log/test 설정 예 

$ cat /etc/logrotate.d/test1   // kill -HUP  syslogd 재시작 
/var/log/messages /var/log/test {
    missingok              ## 로그파일을 없어도, 에러없이 진행 (nomissingok 로그파일 없으면 에러발생 default) 
    monthly
    rotate 5
    dateext                ## 저장된 log에 날짜표시 
#   ifempty                ## ifempty 이 default (log파일 비어 있어도 rotate 진행)    
#   nosharedscripts        ## nosharedscripts 이 default 이며, 상위 /var/log/test*이면 여러개를 다 실행 
    postrotate             ## logroate가 실행된 후 script로 kill로 Signal 전송하여 Refresh 
        /bin/kill -HUP 'cat /var/run/syslogd.pid 2> /dev/null' 2> /dev/null
    endscript

$ kill -l
HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH IO PWR SYS RTMIN RTMIN+1 RTMIN+2 RTMIN+3 RTMIN+4 RTMIN+5 RTMIN+6 RTMIN+7 RTMIN+8 RTMIN+9 RTMIN+10 RTMIN+11 RTMIN+12 RTMIN+13 RTMIN+14 RTMIN+15 RTMAX-14 RTMAX-13 RTMAX-12 RTMAX-11 RTMAX-10 RTMAX-9 RTMAX-8 RTMAX-7 RTMAX-6 RTMAX-5 RTMAX-4 RTMAX-3 RTMAX-2 RTMAX-1 RTMAX
## 1 -HUP SIGHUP  Refresh
##-2 INT  SIGINT Interrupt (Ctrl+C)
##-3 QUIT SIGQUIT Quit (Ctrl+z)  (이하 생략)

}

Process에 문제가 있어서 다시 시작하고자 할 때에는 SIGHUP, SIGTERM, SIGKILL
kill 명령어 
  https://ko.wikipedia.org/wiki/Kill
  https://en.wikipedia.org/wiki/SIGHUP


$ cat /etc/logrotate.d/test2   // kill -HUP  syslogd 재시작 
/var/log/test {
    missingok
    daily
    rotate 10
    dateext
    sharedscripts          ## sharedscripts /var/log/news/*  같이 log가 여러개 중복될때 한번만실행 
    postrotate
         /bin/kill -HUP 'cat /var/run/syslogd.pid 2> /dev/null' 2> /dev/null
    endscript
}

/var/log/messages {
    missingok
    daily
    rotate 20
    dateext    
    postrotate
        /bin/kill -HUP 'cat /var/run/syslogd.pid 2> /dev/null' 2> /dev/null
    endscript
}

$ cat /etc/logrotate.d/test3   // kill -HUP  syslogd 재시작 

/var/log/messages {
    rotate 3
    minsize 1M
    sharedscripts
    postrotate
        /bin/kill -HUP 'cat /var/run/syslogd.pid 2> /dev/null' 2> /dev/null
    endscript
}

$ cat /var/lib/logrotate.status  // 각 logroate가 된 후 상태확인 
logrotate state -- version 2
"/var/log/test" 2020-08-06-0:0:58
"/var/log/wtmp" 2020-07-10-11:0:0
"/var/log/btmp" 2020-07-10-11:0:0
"/var/log/message" 2020-07-10-11:0:0


logrotate(cron) and syslogd
  http://heart4u.co.kr/tblog/370
  



2. systemd 의 timer 사용방법


SysVinit에서는 cron을 사용했지만, systemd에서는 Timer를 사용해서 본인이 원하는 것을 주기적으로 실행해야하며, 
systemd의 timer는 monotonic timercalendar time(Realtime)으로 구분이되는데, 거의 monotonic timer를 많이 사용할 것이므로 아래로 처럼 구성한다


  • 현재 Timer 구성확인
$ systemctl list-timers   // Timer 상태확인 
or
$ systemctl list-timers --all | cat   // 모든 Timer 상태확인 (inactive timer 포함)
or
$ systemctl list-timers | cat
NEXT                         LEFT     LAST                         PASSED  UNIT                         ACTIVATES
Tue 2020-07-17 17:44:51 KST  7h left  Mon 2020-07-16 17:44:51 KST  16h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Wed 2020-07-18 00:00:00 KST  14h left Tue 2020-07-17 00:00:36 KST  9h ago  logrotate.timer              logrotate.service

  • monotonic timer 예제
 $ cat monotonic.timer   // booting 후 15분 후 동작후 매주 돌아가는 timer (미테스트)
[Unit]
Description=Run foo weekly and on boot

[Timer]
OnBootSec=15min
OnUnitActiveSec=1w 

[Install]
WantedBy=timers.target 

  • realtime timer 예제
 $ realtime.timer    // 일주일에 한번  12:00am on Monday 동작 (미테스트) 
[Unit]
Description=Run foo weekly

[Timer]
OnCalendar=weekly
Persistent=true

# Persistent          true이며, Timer가 실행한 서비스 Trigger정보를 Disk가 저장후 Timer가 동작할때, 즉각 Service 동작하도록 설정 

[Install]
WantedBy=timers.target

출처 

Realtime 으로 Monotonic Timer 구현 


2.1 systemd 의 TEST Timer 구성 


Timer를 잘 사용하기 위해서 monotonic timer 기반으로 구성 후 간단한 예제를 만들고, 이를 테스트 할 수 있는 shell script 와 함께 
내부의 환경변수 값들을 변경해보면 직접 테스트 진행. 

  • Test Timer 전체구성 
  1. /usr/bin/foo.sh 
  2. /lib/systemd/system/foo.timer
  3. /lib/systemd/system/foo.service 

  • timer로 실행될 service의 shell script 작성
$ vi /usr/bin/foo.sh   // 주기적으로 실행할 Shell Script 
#!/bin/sh
# Author: Jeonghun
#

WAIT_100MS() {
    sleep 0.1
}
# NOW 현재시간 
# UPTIME 은 부팅후 시간 측정용 1st arg (Booting 후 Sec)  2nd arg (Idle Time Sec) 
NOW=$(date +"%H:%M:%S")
UPTIME=`cat /proc/uptime`
echo -e "\x1b[1;93m *** TIMER $NOW  $UPTIME  \x1b[0m" > /dev/kmsg

$ chmod +x /usr/bin/foo.sh  // 실행권한 설정 

  • systemd 의 foo.timer 작성 (sample)
$ vi /etc/systemd/system/foo.timer
or
$ vi /lib/systemd/system/foo.timer
[Unit]
Description=Run 3 Secs foo timer from 1 min after boot

[Timer]
OnBootSec=1min
OnUnitActiveSec=3s
#OnActiveSec=3s

[Install]
WantedBy=timers.target

#
#
# [Timer] Section 
#
# 반드시 Monotonic Timer로 설정시 아래 설정을 포함을 해야하며, 중복 설정도 가능 
# OnActiveSec=, OnBootSec=, OnStartupSec=, OnUnitActiveSec=, OnUnitInactiveSec= 
# 
# 상위 설정을 한 후 세부 설정을 별도로 진행


$ systemctl enable foo.timer
Created symlink /etc/systemd/system/timers.target.wants/foo.timer → /etc/systemd/system/foo.timer.

$ ls -al /etc/systemd/system/timers.target.wants/foo.timer   // 생성된 링크확인 

  https://www.freedesktop.org/software/systemd/man/systemd.timer.html

  • systemd 의 timer 에 의해 실행될 service 설정
$ vi /etc/systemd/system/foo.service    
or
$ vi /lib/systemd/system/foo.service  // 상위 foo.timer에 의해 실행되므로 별도로 service 설정을 안해도 됨 
[Unit]
Description=TEST
Requires=local-fs.target
After=local-fs.target

[Service]
Type=simple
ExecStart=/usr/bin/foo.sh

Test Timer 구성참조
  https://coderoad.ru/9619362/%D0%97%D0%B0%D0%BF%D1%83%D1%81%D0%BA-a-cron-%D0%BA%D0%B0%D0%B6%D0%B4%D1%8B%D0%B5-30-%D1%81%D0%B5%D0%BA%D1%83%D0%BD%D0%B4 

AccuracySec=의 이해 

2.2 TEST(foo.timer) 설정 변경 테스트  

Timer의 설정의 의미를 알기 위해서 foo.timer의 설정값에 변경하여 개별 테스트 진행하며, 아래와 같이 그 결과도 알아본다.


  • foo.timer 수정 후 테스트 및 분석 (전체 2번실행)

[Timer]
OnBootSec=1min
OnActiveSec=3s
# OnBootSec          Booting 후 실행되는 Timer 설정 1분이며, OnStartupSec= 값도 동일하게 설정
# OnStartupSec       OnBootSec 거의 유사하지만 다른점은 service manager가 동작한 시점부터라고함 (아직 정확한 차이는 모름)
# OnActiveSec        Timer 자신이 Active 된 순간의 시간이라고 하는데,  
# AccuracySec        Timer를 확인하는 주기 미설정시 기본 1분 
// 1st TEST, 상위설정값 TEST 많이 다르며, 전체 2번만 동작  
 *** TIMER 14:55:59  14.64 0.77               // OnActiveSec 14초 Timer가 Active되는 순간 
 *** TIMER 14:56:55  70.89 47.92              // OnBootSec 70초 (AccuracySec 1분, 정확성이 떨어짐) 

// 2nd TEST, 상위설정값 TEST 많이 다르며, 전체 2번만 동작  
 *** TIMER 15:21:56  14.81 0.90
 *** TIMER 15:22:53  72.45 50.00              // OnBootSec 72초 (AccuracySec 1분, 정확성이 떨어짐) 

  • foo.timer 수정 후 테스트 및 분석 (전체 1번실행)

[Timer]
OnBootSec=1min
AccuracySec=3s
# AccuracySec        Timer를 확인하는 주기를 설정이며, 기본은 1분이지만,3초로 변경, 즉 정확성을 높임  
// 1st TEST 상위 설정값대로 동작하며, 전체 1번만 동작   
 *** TIMER 15:33:52  62.05 39.34                // OnBootSec 62초 (AccuracySec 3초 이내) 
 
// 2nd TEST 상위 설정값대로 동작하며, 전체 1번만 동작   
 *** TIMER 15:38:43  62.95 40.51                // OnBootSec 62초 (AccuracySec 3초 이내)       



  • foo.timer 수정 후 테스트 및 분석 (계속실행)
[Timer]
OnBootSec=1min
OnUnitActiveSec=3s
# OnBootSec          Booting 후 실행되는 Timer 설정 1분이며, OnStartupSec= 값도 동일하게 설정 
# OnUnitActiveSec    마지막(OnBootSec)에 Timer가 실행한 Unit(즉 서비스) Active가 되었을때 기준으로 3초 설정 
# AccuracySec        Timer를 확인하는 주기 미설정시 기본 1분 
// 1st TEST, 상위설정값과 많이 다르지만 계속동작                         
 *** TIMER 14:51:42  73.61 50.83           // OnBootSec 73초 (AccuracySec 때문에 정확성이 떨어짐) 
 *** TIMER 14:52:19  111.12 87.40          // OnUnitActiveSec 111-73= 38초 
 *** TIMER 14:52:43  134.63 110.23         // OnUnitActiveSec 134-111= 23초 
 *** TIMER 14:53:01  153.08 128.23         // OnUnitActiveSec 153-134= 19초 
 *** TIMER 14:53:19  171.37 146.09

// 2nd TEST, 상위설정값과 많이 다르지만 계속동작       
 *** TIMER 15:02:58  78.25 55.14            // OnBootSec 78초 
 *** TIMER 15:03:32  111.50 87.60
 *** TIMER 15:04:04  143.61 118.85
 *** TIMER 15:04:19  158.80 133.61
 *** TIMER 15:04:33  173.25 147.56


  • foo.timer 수정 후 테스트 및 분석 (계속실행)

[Timer]
OnBootSec=1min
OnUnitActiveSec=3s
AccuracySec=3s
# OnUnitActiveSec    마지막(OnBootSec)에 Timer가 실행한 Unit(즉 서비스) Active가 되었을때 기준으로 3초 설정 
# OnUnitInactiveSec  마지막(OnBootSec)에 Timer가 실행한 Unit(즉 서비스) inActive가 되었을때 기준으로 시간설정 
# AccuracySec        Timer를 확인하는 주기를 설정이며, 기본은 1분이지만,3초로 변경, 즉 정확성을 높임  
// 1st TEST 상위 설정값과 유사하게 동작 (3~6 초)   
 *** TIMER 15:14:11  60.66 37.90              // OnBootSec 60초 (AccuracySec 3초 이내) 
 *** TIMER 15:14:17  66.65 43.66              // OnUnitActiveSec 3초 (AccuracySec 3초 이내) 
 *** TIMER 15:14:21  70.65 47.49
 *** TIMER 15:14:27  76.65 53.23
 *** TIMER 15:14:31  80.65 57.03
 *** TIMER 15:14:37  86.64 62.80
 *** TIMER 15:14:41  90.64 66.65
 *** TIMER 15:14:47  96.65 72.41
 *** TIMER 15:14:51  100.64 76.23
 *** TIMER 15:14:57  106.65 81.98
 *** TIMER 15:15:01  110.64 85.82
 *** TIMER 15:15:07  116.65 91.63
 *** TIMER 15:15:11  120.65 95.41
 *** TIMER 15:15:14  123.70 98.31
 *** TIMER 15:15:20  129.65 103.99
 
// 2nd TEST 상위 설정값과 유사하게 동작 (3~6 초)   
 *** TIMER 15:18:38  62.95 40.17             // OnBootSec 62초 (AccuracySec 3초 이내) 
 *** TIMER 15:18:44  68.94 45.94             // OnUnitActiveSec 3초 (AccuracySec 3초 이내) 
 *** TIMER 15:18:50  74.94 51.72
 *** TIMER 15:18:54  78.95 55.56
 *** TIMER 15:19:00  84.95 61.33
 *** TIMER 15:19:04  88.95 65.16
 *** TIMER 15:19:10  94.95 70.94
 *** TIMER 15:19:14  98.94 74.77
 *** TIMER 15:19:20  104.94 80.57
 *** TIMER 15:19:24  108.94 84.39
 *** TIMER 15:19:30  114.95 90.17
 *** TIMER 15:19:34  118.94 94.01
 *** TIMER 15:19:40  124.94 99.87
 *** TIMER 15:19:44  128.95 103.73

  • foo.timer 수정 후 테스트 및 분석 (계속실행)

[Timer]
OnBootSec=1min
OnUnitActiveSec=1s
AccuracySec=3s
# OnUnitActiveSec    마지막(OnBootSec)에 Timer가 실행한 Unit(즉 서비스) Active가 되었을때 기준으로 1초 설정 
# AccuracySec        Timer를 확인하는 주기를 설정이며, 기본은 1분이지만,3초로 변경, 즉 정확성을 높임  
// 1st TEST 상위 설정값과 가장유사하게 동작 2-4초 간격   
 *** TIMER 15:27:55  61.66 38.97               // OnBootSec 61초 (AccuracySec 3초 이내) 
 *** TIMER 15:27:59  65.65 42.82               // OnUnitActiveSec 1초 (AccuracySec 3초 이내) 
 *** TIMER 15:28:03  69.65 46.64
 *** TIMER 15:28:05  71.66 48.50
 *** TIMER 15:28:09  75.65 52.26
 *** TIMER 15:28:13  79.65 56.07
 *** TIMER 15:28:15  81.65 57.98
 *** TIMER 15:28:19  85.65 61.80
 *** TIMER 15:28:23  89.66 65.65
 *** TIMER 15:28:25  91.65 67.52
 *** TIMER 15:28:29  95.65 71.35
 *** TIMER 15:28:33  99.66 75.17
 *** TIMER 15:28:35  101.65 77.06
 *** TIMER 15:28:39  105.65 80.92
 *** TIMER 15:28:43  109.65 84.74
 *** TIMER 15:28:45  111.65 86.63
 *** TIMER 15:28:49  115.66 90.47
 *** TIMER 15:28:53  119.65 94.29
 *** TIMER 15:28:55  121.66 96.18
 *** TIMER 15:28:59  125.66 99.99
 
// 2nd TEST 상위 설정값과 가장유사하게 동작   
 *** TIMER 15:30:39  63.05 40.48               // OnBootSec 63초 (AccuracySec 3초 이내) 
 *** TIMER 15:30:43  67.04 44.33               // OnUnitActiveSec 1초 (AccuracySec 3초 이내) 
 *** TIMER 15:30:47  71.04 48.17
 *** TIMER 15:30:50  74.04 51.06
 *** TIMER 15:30:54  78.04 54.87
 *** TIMER 15:30:58  82.04 58.69
 *** TIMER 15:30:59  83.30 59.84
 *** TIMER 15:31:03  87.04 63.42
 *** TIMER 15:31:07  91.04 67.23
 *** TIMER 15:31:10  94.04 70.10
 *** TIMER 15:31:14  98.04 73.91
 *** TIMER 15:31:18  102.04 77.74
 *** TIMER 15:31:20  104.04 79.61
 *** TIMER 15:31:24  108.04 83.43
 *** TIMER 15:31:28  112.04 87.29
 *** TIMER 15:31:30  114.04 89.20
 *** TIMER 15:31:34  118.04 93.01
 *** TIMER 15:31:38  122.04 96.81
 *** TIMER 15:31:40  124.04 98.69
 *** TIMER 15:31:42  126.39 100.92
 *** TIMER 15:31:46  130.04 104.45


2.3 systemd 의 timer 와 service 동시 설정


지금까지는 상위 service는 timer가 실행을 해줄 때까지 기다리고 동작을 했지만, service도 별도로 동작가능하도록 설정하여, 동시 설정하여 사용하자  

주로 이렇게 사용하는 이유는 booting 후 꼭 실행을 해야하는 경우가 될 것 같다. 


  • systemd의 timer 와 service 두개 중복 설정 
// 상위 foo.timer의 실행과 foo.service 독자적인 실행으로 중복실행방법  
$ vi /etc/systemd/system/foo.service    
or
$ vi /lib/systemd/system/foo.service  
[Unit]
Description=TEST
Requires=local-fs.target
After=local-fs.target

[Service]
Type=simple
ExecStart=/usr/bin/foo.sh

[Install]
WantedBy=multi-user.target

$ systemctl enable foo.service  // 이제 foo.service로도 동작가능 (timer가 죽어도 동작가능)  
Created symlink /etc/systemd/system/multi-user.target.wants/foo.service → /etc/systemd/system/foo.service.

$ ls -al /etc/systemd/system/multi-user.target.wants/foo.service   // 생성된 링크확인 


댓글 없음 :