Search
Duplicate
πŸ’§

무쀑단 배포 with jenkins

생성일
2022/11/04
νƒœκ·Έ
infra
linux
jenkins
λͺ©μ°¨

λ°°κ²½

λͺ¨λ½ μ„œλΉ„μŠ€λ₯Ό λ°°ν¬ν•œ 이후, μ‚¬μš©μžμ˜ ν”Όλ“œλ°±κ³Ό μΆ”κ°€ κΈ°λŠ₯듀을 ν•¨κ»˜ λ°˜μ˜ν•˜λ©° 운영 μ„œλ²„λ‘œ 자주 λ°°ν¬ν•˜κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 이 κ³Όμ •μ—μ„œ κΈ°μ‘΄ λ²„μ „μ˜ 운영 μ„œλ²„λ₯Ό μ’…λ£Œμ‹œν‚€κ³ , μƒˆλ‘œμš΄ λ²„μ „μ˜ 운영 μ„œλ²„λ₯Ό μ‹€ν–‰μ‹œν‚€λ©° μ•½ 20초 μ •λ„μ˜ λ‹€μš΄νƒ€μž„μ΄ λ°œμƒν•˜κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 이 20초 κ°„μ˜ λ‹€μš΄νƒ€μž„λ„ λ¬Έμ œμ˜€μ§€λ§Œ, 개발 μ„œλ²„μ—μ„œ 미처 μ•Œμ•„μ°¨λ¦¬μ§€ λͺ»ν•œ μ—λŸ¬κ°€ λ°œμƒν•  λ•Œλ„ λ¬Έμ œκ°€ μžˆμ—ˆμŠ΅λ‹ˆλ‹€. 저희 ν”„λ‘œμ νŠΈμ—μ„œ λ°°ν¬ν•˜λŠ” 방식은 κΈ°μ‘΄ λ²„μ „μ˜ jar νŒŒμΌμ„ μƒˆλ‘œμš΄ λ²„μ „μ˜ jar 파일둜 μ˜€λ²„λΌμ΄λ“œν•˜κ³  μƒˆλ‘œμš΄ λ²„μ „μ˜ jar νŒŒμΌμ„ μ‹€ν–‰ν•˜λŠ” λ°©μ‹μ΄μ—ˆμŠ΅λ‹ˆλ‹€. μ΄λŸ¬ν•œ λ°©μ‹μœΌλ‘œ 인해 μƒˆλ‘œμš΄ λ²„μ „μ—μ„œ μ—λŸ¬κ°€ λ°œμƒν•œλ‹€λ©΄ 이전 λ²„μ „μ˜ jar 파일이 μ—†μ–΄, λ‘€λ°±ν•˜κΈ° μœ„ν•΄μ„œλŠ” 이전 λ²„μ „μ˜ jar 파일둜 λ‹€μ‹œ μ˜€λ²„λΌμ΄λ“œν•˜μ—¬ μ„œλ²„λ₯Ό μ‹€ν–‰ν•΄μ•Ό ν–ˆμŠ΅λ‹ˆλ‹€. 이와 같은 이유둜 ν•œλ²ˆ μ—λŸ¬κ°€ λ°œμƒν•˜λ©΄ λ‹€μš΄νƒ€μž„μ΄ κΈΈμ–΄μ Έμ„œλΉ„μŠ€ μš΄μ˜μ— 치λͺ…μ μœΌλ‘œ λ‹€κ°€μ˜€κ²Œ λ©λ‹ˆλ‹€. 이에 따라 μ•ˆμ •μ μΈ μ„œλΉ„μŠ€ μš΄μ˜μ„ μœ„ν•΄ λ‹€μš΄νƒ€μž„μ„ μ΅œμ†Œν™”μ‹œν‚€κ³ , λΉ λ₯΄κ²Œ λ‘€λ°±ν•  수 μžˆλŠ” 무쀑단 배포 방식 을 μ μš©ν•˜κ³ μž ν•˜μ˜€μŠ΅λ‹ˆλ‹€. λ˜ν•œ μ„œλ²„κ°€ 예기치 λͺ»ν•˜κ²Œ μ’…λ£Œλ˜μ—ˆμ„ λ•Œλ₯Ό λŒ€λΉ„ν•˜κ³  νŠΈλž˜ν”½ 뢄산을 μœ„ν•΄ λ‘œλ“œ λ°ΈλŸ°μ‹± ν™˜κ²½λ„ ν•¨κ»˜ μ μš©ν•΄ λ³΄κ² μŠ΅λ‹ˆλ‹€.

무쀑단 배포 μ „λž΅

무쀑단 배포 μ „λž΅μ—λŠ” 둀링 방식, 블루/κ·Έλ¦° 방식, μΉ΄λ‚˜λ¦¬μ•„ 방식 λ“± 크게 μ„Έ 가지가 μžˆμŠ΅λ‹ˆλ‹€. 각 방식에 λŒ€ν•œ 세뢀적인 λ‚΄μš©μ€ ν›„λ””μ˜ 무쀑단 배포 μ•„ν‚€ν…μ²˜μ™€ 배포 μ „λž΅ ν¬μŠ€νŒ…μ— μžμ„Έν•˜κ²Œ λ‚˜μ™€μžˆκΈ°μ— 이 ν¬μŠ€νŒ…μ—μ„œλŠ” μƒλž΅ν•˜κ² μŠ΅λ‹ˆλ‹€. 저희 λͺ¨λ½ ν”„λ‘œμ νŠΈμ—μ„œ μ„ νƒν•œ 무쀑단 배포 μ „λž΅μ€ 블루/κ·Έλ¦° λ°©μ‹μž…λ‹ˆλ‹€. μ΄μœ λŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.
β€’
둀링, μΉ΄λ‚˜λ¦¬μ•„ 방식은 ν˜Έν™˜μ„±μ˜ λ¬Έμ œκ°€ μžˆμ„ 것이라 νŒλ‹¨
μΉ΄λ‚˜λ¦¬μ•„μ™€ 둀링 방식은 점차 μƒˆλ‘œμš΄ 버전을 λ„μž…ν•˜κΈ°λ•Œλ¬Έμ—, ꡬ 버전과 μ‹  버전이 λ™μ‹œμ— μš΄μ˜λ˜λŠ” 타이밍이 μ‘΄μž¬ν•˜κ³ , μ΄λŠ” ν˜Έν™˜μ„±μ˜ 문제λ₯Ό μ•ΌκΈ°ν•  수 μžˆλ‹€κ³  μƒκ°ν–ˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ λ™μ‹œμ— μš΄μ˜λ˜λŠ” μ‹œμ μ—μ„œ μ‚¬μš©μžκ°€ μƒˆλ‘œμš΄ λ²„μ „μ—μ„œ μƒˆλ‘œ μΆ”κ°€λœ 화면을 μš”μ²­ν•˜λŠ” λ²„νŠΌμ„ λˆŒλ €μ„ λ•Œ, ꡬ λ²„μ „μ˜ μ„œλ²„λ‘œ μš”μ²­μ΄ λ“€μ–΄κ°€λ©΄ 404 νŽ˜μ΄μ§€κ°€ 응닡될 κ²ƒμž…λ‹ˆλ‹€. 반면 블루/κ·Έλ¦° 방식은 μƒˆλ‘œμš΄ 버전이 ν•œλ²ˆμ— μ‹€ν–‰λ˜κΈ°λ•Œλ¬Έμ—, reload ν•˜λŠ” νƒ€μ΄λ°λ§Œ ν˜Έν™˜μ„± λ¬Έμ œκ°€ λ°œμƒν•˜μ—¬ ν˜Έν™˜μ„± μΈ‘λ©΄μ—μ„œ μž₯점이 μžˆλ‹€κ³  νŒλ‹¨ν–ˆμŠ΅λ‹ˆλ‹€.
β€’
블루/κ·Έλ¦° 방식은 둀백이 쉬움
μ„œλΉ„μŠ€ νŠΉμ„± 상 배포λ₯Ό 자주 ν•˜κΈ°λ•Œλ¬Έμ—, 배포 κ³Όμ •μ—μ„œ μƒˆλ‘œμš΄ λ²„μ „μ˜ μ—λŸ¬κ°€ λ°œμƒν•  κ°€λŠ₯성도 λ†’μ•„μ§‘λ‹ˆλ‹€. 이에 따라 둀백을 ν•΄μ•Ό ν•˜λŠ” 상황도 λ§ˆμ£Όν•΄μ•Ό ν•˜λŠ”λ°, 블루/κ·Έλ¦° 배포 방식은 ꡬ λ²„μ „μ˜ μ„œλ²„κ°€ λ°±μ—…μš©μœΌλ‘œ λ– μžˆμ„ 수 있고, λ˜λŠ” λ°±μ—…λœ ꡬ λ²„μ „μ˜ jar νŒŒμΌμ„ λ°”λ‘œ μ‹€ν–‰ μ‹œν‚¬ μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. κ·Έλž˜μ„œ λ‘€λ°± 상황 μ‹œ, λΌμš°νŒ…λ§Œ ꡬ λ²„μ „μ˜ μ„œλ²„λ‘œ λ°”κΏ”μ£Όκ±°λ‚˜ ꡬ λ²„μ „μ˜ jar νŒŒμΌμ„ μ‹€ν–‰μ‹œμΌœ λΌμš°νŒ…μ„ λ°”κΏ”μ£Όλ©΄ 둀백을 μ‰½κ²Œ ν•  수 μžˆκΈ°λ•Œλ¬Έμ—, λ‘€λ°± κ΄€μ μ—μ„œλ„ 저희 μ„œλΉ„μŠ€μ™€ 잘 λ§žλŠ”λ‹€κ³  νŒλ‹¨ν–ˆμŠ΅λ‹ˆλ‹€.
λ¬Όλ‘  블루/κ·Έλ¦° 배포 방식은 λ¦¬μ†ŒμŠ€κ°€ 2배둜 λ“€μ–΄κ°„λ‹€λŠ” 단점이 μžˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ μΆ”κ°€μ μœΌλ‘œ μ‚¬μš©ν•  μΈμŠ€ν„΄μŠ€λŠ” t4g micro 이기에 λΉ„μš©μ΄ μƒλŒ€μ μœΌλ‘œ 적게 μ†Œλͺ¨λ˜κ³ , ν•˜λ‚˜μ˜ μΈμŠ€ν„΄μŠ€λ₯Ό 더 μ‚¬μš©ν•˜λŠ” λΉ„μš©λ³΄λ‹€ 블루/κ·Έλ¦° 방식을 μ‚¬μš©ν•˜λ©΄μ„œ 얻을 이점이 더 λ†’λ‹€κ³  νŒλ‹¨ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

λ‘œλ“œ λ°ΈλŸ°μ‹±

무쀑단 배포 ν™˜κ²½μ„ μ μš©ν•˜λ©΄μ„œ, μ•ˆμ •μ μΈ μ„œλΉ„μŠ€ μ œκ³΅μ„ μœ„ν•΄ λ‘œλ“œ λ°ΈλŸ°μ‹± ν™˜κ²½λ„ ν•¨κ»˜ ꡬ좕해 λ³΄μ•˜μŠ΅λ‹ˆλ‹€. λ‘œλ“œ λ°ΈλŸ°μ‹± ν™˜κ²½μ€ μ„œλ²„κ°€ μ²˜λ¦¬ν•΄μ•Ό ν•  μš”μ²­μ„ μ—¬λŸ¬ λŒ€μ˜ μ„œλ²„λ‘œ λ‚˜λˆ„μ–΄ μ²˜λ¦¬ν•  수 μžˆλŠ” ν™˜κ²½μ„ μ˜λ―Έν•©λ‹ˆλ‹€. λ‘œλ“œ λ°ΈλŸ°μ‹±μ€ νŠΈλž˜ν”½μ„ λ‚˜λˆ„μ–΄ μ²˜λ¦¬ν•˜κΈ°λ•Œλ¬Έμ— ν•˜λ‚˜μ˜ μ„œλ²„κ°€ μ²˜λ¦¬ν•˜λŠ” λΆ€ν•˜λ₯Ό 쀄일 수 있고, 만일의 상황에 ν•˜λ‚˜μ˜ μ„œλ²„κ°€ μ˜ˆμƒμΉ˜ λͺ»ν•˜κ²Œ λ‹€μš΄λ˜μ—ˆμ„ λ•Œ, 남은 μ„œλ²„λ‘œ μ„œλΉ„μŠ€λ₯Ό μ§€μ†μ μœΌλ‘œ μ΄μš©ν•  수 μžˆλ„λ‘ λŒ€λΉ„ν•  수 μžˆλŠ” ν™˜κ²½μ„ λ§Œλ“œλŠ” 이점도 μžˆμŠ΅λ‹ˆλ‹€.

인프라 ꡬ쑰

저희 ν”„λ‘œμ νŠΈμ—μ„œλŠ” μœ„μ™€ 같이 블루/κ·Έλ¦° 무쀑단 배포 방식과 λ‘œλ“œ λ°ΈλŸ°μ‹±μ„ λͺ¨λ‘ μ μš©μ‹œν‚€κΈ°λ‘œ ν•˜μ˜€κ³ , μ΄λŸ¬ν•œ ν™˜κ²½μ„ λ‹¨μˆœνžˆ μ μš©ν•˜λ©΄ 총 4개의 μΈμŠ€ν„΄μŠ€λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.
이 방식은 μš΄μ˜λ˜μ§€ μ•ŠλŠ” μΈμŠ€ν„΄μŠ€κ°€ 2κ°œλ‚˜ μžˆκΈ°λ•Œλ¬Έμ—, λ¦¬μ†ŒμŠ€ ν™œμš© μΈ‘λ©΄μ—μ„œ λ‹€μ†Œ λΉ„νš¨μœ¨μ μ΄λΌκ³  νŒλ‹¨ν•˜μ˜€μŠ΅λ‹ˆλ‹€. 이에 따라 적은 λ¦¬μ†ŒμŠ€λ‘œ μ΅œλŒ€ν•œ ν™œμš©ν•  수 μžˆλŠ” 방법을 μƒκ°ν•΄λ³΄μ•˜κ³  λ‹€μŒκ³Ό 같은 인프라 ꡬ쑰λ₯Ό λ– μ˜¬λ ΈμŠ΅λ‹ˆλ‹€.
두 개의 μΈμŠ€ν„΄μŠ€ λͺ¨λ‘ 8080, 8081 포트λ₯Ό ν™œμš©ν•˜μ—¬ 총 4개의 μ•± μ„œλ²„κ°€ κ΅¬λ™λ©λ‹ˆλ‹€. 그리고 각 ν¬νŠΈλŠ” μ‹  버전 λ˜λŠ” ꡬ 버전을 κ°€μ§‘λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ ν˜„μž¬ 두 λŒ€μ˜ WAS 8080 ν¬νŠΈμ— μ‹  버전이 μ‹€ν–‰λ˜κ³  μžˆλ‹€λ©΄, 8081 ν¬νŠΈμ—λŠ” ꡬ 버전이 μ‹€ν–‰λ˜κ³  μžˆλŠ” κ²ƒμž…λ‹ˆλ‹€. 이 λ•Œ 블루 μ„œλ²„μΈ 8081ν¬νŠΈλŠ” μš΄μ˜λ˜μ§„ μ•Šκ³ , jar 파일둜 λ°±μ—… 파일만 μ‘΄μž¬ν•©λ‹ˆλ‹€.
배포 과정은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.
1.
μƒˆλ‘œμš΄ 배포λ₯Ό 진행할 λ•Œ μš΄μ˜λ˜μ§€ μ•ŠλŠ” 포트λ₯Ό ν™•μΈν•©λ‹ˆλ‹€.
2.
μš΄μ˜λ˜μ§€ μ•ŠλŠ” ν¬νŠΈμ— μƒˆλ‘œμš΄ λ²„μ „μ˜ 배포λ₯Ό μ§„ν–‰ν•©λ‹ˆλ‹€.
3.
μƒˆλ‘œμš΄ λ²„μ „μ˜ μ„œλ²„κ°€ κ΅¬λ™λœ 것을 ν™•μΈν•˜λ©΄ ν˜„μž¬ μš΄μ˜μ€‘μΈ λ‹€λ₯Έ 포트의 μ„œλ²„λ₯Ό μ’…λ£Œν•©λ‹ˆλ‹€.
즉 각각의 포트둜 블루/κ·Έλ¦° 배포 방식을 μ μš©ν•˜κ³ , 두 개의 μΈμŠ€ν„΄μŠ€λ‘œ λ‘œλ“œ λ°ΈλŸ°μ‹±μ„ μ μš©ν•˜λŠ” κ΅¬μ‘°μž…λ‹ˆλ‹€. μœ„μ˜ ꡬ쑰λ₯Ό μ μš©ν•˜μ—¬ 2개의 μΈμŠ€ν„΄μŠ€λ§ŒμœΌλ‘œ 블루/κ·Έλ¦° 배포 방식과 λ‘œλ“œ λ°ΈλŸ°μ‹±μ˜ 이점을 λͺ¨λ‘ κ°€μ Έμ˜¬ 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

μ μš©ν•˜κΈ°

DB μ„€μ •

κ°€μž₯ λ¨Όμ € μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€μ—μ„œλ„ DB 에 접근이 κ°€λŠ₯ν•˜λ„λ‘ DB 호슀트둜 μΆ”κ°€ν•΄μ£Όμ—ˆμŠ΅λ‹ˆλ‹€. 같은 μ„œλΈŒλ„· λ„€νŠΈμ›Œν¬μ— μžˆκΈ°λ•Œλ¬Έμ— private IP 둜 μΆ”κ°€ν•΄ μ£Όμ—ˆμŠ΅λ‹ˆλ‹€.

jenkins νŒŒμ΄ν”„λΌμΈ

기쑴에 저희 ν”„λ‘œμ νŠΈμ—μ„œλŠ” 배포λ₯Ό jenkins 의 ν”„λ¦¬μŠ€νƒ€μΌμ„ ν™œμš©ν•΄ μ§„ν–‰ν–ˆμ—ˆμŠ΅λ‹ˆλ‹€. ν”„λ‘œμ νŠΈ μ΄ˆκΈ°μ— 배포 μžλ™ν™”λ₯Ό μ μš©ν•  λ•Œ νŒŒμ΄ν”„λΌμΈ 방식보닀 μ‹ μ†ν•˜κ²Œ ν•  수 μžˆμ—ˆκΈ°λ•Œλ¬Έμž…λ‹ˆλ‹€. ν•˜μ§€λ§Œ 무쀑단 배포와 λ‘œλ“œ λ°ΈλŸ°μ‹± λ“± 배포 과정이 점점 λ³΅μž‘ν•΄μ§€λ©΄μ„œ ν”„λ¦¬μŠ€νƒ€μΌ λ°©μ‹μœΌλ‘œλŠ” μ μš©ν•˜κΈ° νž˜λ“€κ² λ‹€κ³  νŒλ‹¨ν•˜μ˜€κ³ , 배포 μžλ™ν™” 방식을 ν”„λ¦¬μŠ€νƒ€μΌ λ°©μ‹μ—μ„œ νŒŒμ΄ν”„λΌμΈ λ°©μ‹μœΌλ‘œ λ³€κ²½ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
jenkins νŒŒμ΄ν”„λΌμΈμ€ groovy μ–Έμ–΄μ˜ DSL둜 μž‘μ„±ν•˜λ©°Β declarative 방식과 scripted 방식이 μžˆμŠ΅λ‹ˆλ‹€.
β€’
declarative 방식
μ΅œμƒλ‹¨μ΄ 'pipeline' 으둜 μ‹œμž‘ν•˜κ³ , 기쑴에 μ •μ˜λœ 문법과 ν˜•μ‹μ— 따라 μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ 비ꡐ적 결과물은 κ°„λ‹¨ν•˜μ§€λ§Œ, λ‘œμ§μ„ μΆ”κ°€ν•˜λ €λ©΄ jenkins와 plugin이 μ œκ³΅ν•˜λŠ” 문법을 λͺ¨λ‘ μˆ™μ§€ν•΄μ•Όν•©λ‹ˆλ‹€.
β€’
scripted 방식
μ΅œμƒλ‹¨μ΄ 'node'둜 μ‹œμž‘ν•˜κ³ , μ΅œμ†Œν•œμ˜ 문법과 ν˜•μ‹λ§Œ 따라 μž‘μ„±ν•©λ‹ˆλ‹€. μ½”λ“œλŠ” 비ꡐ적 λ³΅μž‘ν•˜μ§€λ§Œ, μƒλŒ€μ μœΌλ‘œ 자유둭게 둜직의 좔가와 μˆ˜μ •μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€.
저희 ν”„λ‘œμ νŠΈμ—μ„œλŠ” μ΅œμ†Œν•œμ˜ λ¬Έλ²•λ§Œ μˆ™μ§€ν•˜λ©΄ 자유둭게 μ‚¬μš©ν•  수 μžˆλŠ” scripted 방식을 μ‚¬μš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
이 ν¬μŠ€νŒ…μ—μ„œλŠ” 전체 μŠ€ν¬λ¦½νŠΈλŠ” λ„ˆλ¬΄ κΈΈκΈ°λ•Œλ¬Έμ—, 가독성을 μœ„ν•΄ 각각의 stage 에 λŒ€ν•œ 섀정을 μ„€λͺ…ν•˜κ² μŠ΅λ‹ˆλ‹€. 전체 μŠ€ν¬λ¦½νŠΈλŠ” jenkins prdμ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.
stage('check-out') { // Get some code from a GitHub repository slackSend (channel: "jenkins", color: "good", message: "Build Started - ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)") git url: 'https://github.com/woowacourse-teams/2022-mo-rak.git', branch: 'main' }
Groovy
볡사
check-out stage μ—μ„œλŠ” git λͺ…λ Ήμ–΄λ‘œ github μ—μ„œ μ½”λ“œλ₯Ό κ°€μ Έμ˜΅λ‹ˆλ‹€. slackSend 둜 μŠ¬λž™μ— μ•Œλ¦Όμ„ 보낼 수 μžˆμŠ΅λ‹ˆλ‹€. λ‚΄λΆ€μ˜ ν™˜κ²½ λ³€μˆ˜λŠ” jenkins 에 μ„€μ •λœ ν™˜κ²½ λ³€μˆ˜μž…λ‹ˆλ‹€.
stage('build') { dir ('./backend') { try { sh './gradlew clean build' slackSend (channel: "jenkins", color: "good", message: "Build Succeeded - ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)") } catch(e) { slackSend (channel: "jenkins", color: "warning", message: "Build Failed - ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)") throw e } } }
Groovy
볡사
build stage μ—μ„œλŠ” gradle buildλ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€. dir 둜 λͺ…령을 μˆ˜ν–‰ν•  디렉토리λ₯Ό 지정할 수 μžˆμŠ΅λ‹ˆλ‹€. λ‚΄λΆ€ λΈ”λ‘μ—μ„œλŠ” Groovy λ¬Έλ²•μœΌλ‘œ try/catch λ₯Ό μˆ˜ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ‚΄λΆ€μ˜ sh 은 λ¦¬λˆ…μŠ€ λͺ…령어와 같이 shell script λ₯Ό μˆ˜ν–‰ν•˜λŠ” λͺ…λ Ήμ–΄μž…λ‹ˆλ‹€.
stage('deploy') { try { OUTPUT = sh ( script: '''#!/bin/bash {μˆ˜ν–‰ν•  shell script} ''', returnStdout: true ).trim() OUTPUT = OUTPUT.split("\n") echo "result is : ${OUTPUT}" slackSend (channel: "jenkins", color: "good", message: "Deploy Succeeded - ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)") } catch(e) { slackSend (channel: "jenkins", color: "warning", message: "Deploy Failed - ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)") throw e } }
Groovy
볡사
deploy stage μ—μ„œλŠ” 두 개의 μΈμŠ€ν„΄μŠ€μ— 배포 슀크립트λ₯Ό μ‹€ν–‰μ‹œν‚€κ³  κ·Έ κ²°κ³Ό 받은 κ·Έλ¦° μ„œλ²„ ν¬νŠΈμ— λŒ€ν•œ 정보λ₯Ό λ°›μ•„μ˜΅λ‹ˆλ‹€. script 둜 μˆ˜ν–‰ν•  슀크립트λ₯Ό μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 저희가 μž‘μ„±ν•œ shell script λŠ” λ„ˆλ¬΄ κΈΈμ–΄ μ•„λž˜μ— λ”°λ‘œ λΊμŠ΅λ‹ˆλ‹€. returnStdout 으둜 슀크립트 μˆ˜ν–‰ ν›„ 좜λ ₯λ˜λŠ” λ‚΄μš©μ„ 전달받을 수 있고, 이 좜λ ₯λ˜λŠ” λ‚΄μš©μœΌλ‘œ κ·Έλ¦° μ„œλ²„ 포트 정보λ₯Ό λ°›μ•„μ˜΅λ‹ˆλ‹€.
β€’
μˆ˜ν–‰ν•  shell script
#!/bin/bash IP_LIST=(${PRD_WAS_IP_LIST}) SIZE=${#IP_LIST[@]} PORT_A=8080 PORT_B=8081 PREVIOUS_PORT_LIST=() NEXT_PORT_LIST=() for IP in ${IP_LIST[@]} do if curl -s -I -X OPTIONS "http://${IP}:${PORT_A}" > /dev/null; then PREVIOUS_PORT_LIST+=("${PORT_A}") NEXT_PORT_LIST+=("${PORT_B}") else PREVIOUS_PORT_LIST+=("${PORT_B}") NEXT_PORT_LIST+=("${PORT_A}") fi done for index in $(seq ${SIZE}) do PREVIOUS=${PREVIOUS_PORT_LIST[index-1]} NEXT=${NEXT_PORT_LIST[index-1]} IP=${IP_LIST[index-1]} ssh -i ${PEM_KEY} ubuntu@"${IP}" "sh ${BACKUP_SCRIPT} ${NEXT} 1>/dev/null 2>/dev/null" scp -i ${PEM_KEY} ./backend/build/libs/*.jar ubuntu@"${IP}":/home/ubuntu/deploy 1>/dev/null 2>/dev/null ssh -i ${PEM_KEY} ubuntu@"${IP}" "sh ${DEPLOY_SCRIPT} ${NEXT} 1>/dev/null 2>/dev/null" done for retry in $(seq 10) do COUNT=0 for index in $(seq ${SIZE}) do IP=${IP_LIST[index-1]} NEXT=${NEXT_PORT_LIST[index-1]} if curl -s -I -X OPTIONS "http://${IP}:${PORT_A}" > /dev/null; then COUNT=$(($COUNT + 1)) fi done if [ $COUNT -eq $SIZE ] then break fi if [ $retry -eq 10 ] then exit 1 fi sleep 5 done for index in $(seq ${SIZE}) do echo "${IP_LIST[index-1]}:${NEXT_PORT_LIST[index-1]}" done for index in $(seq ${SIZE}) do echo "${PREVIOUS_PORT_LIST[index-1]}" done
Bash
볡사
β€’
IP_LIST=(${PRD_WAS_IP_LIST})
β—¦
jenkins ν™˜κ²½ λ³€μˆ˜λ‘œ μ €μž₯ν•œ 운영 μ„œλ²„ IP λ¬Έμžμ—΄μ„ () λ₯Ό 톡해 리슀트둜 κ°€μ Έμ˜΅λ‹ˆλ‹€.
β€’
for IP in ${IP_LIST[@]}
β—¦
각 μ„œλ²„μ˜ 8080 ν¬νŠΈμ— curl λͺ…λ Ήμ–΄λ‘œ μš”μ²­μ„ 보내고 응닡이 였면 ν˜„μž¬ κ΅¬λ™λ˜κ³  μžˆλŠ” 포트, μ•„λ‹ˆλ©΄ μ•žμœΌλ‘œ ꡬ동할 포트둜 λ‚˜λˆ„μ–΄ μ €μž₯ν•©λ‹ˆλ‹€.
β€’
for index in $(seq ${SIZE})
β—¦
각 μ„œλ²„μ— ssh λͺ…λ Ήμ–΄λ‘œ μ ‘μ†ν•˜κ³ , sh λͺ…λ Ήμ–΄λ‘œ 각 μ„œλ²„μ— μžˆλŠ” λ°±μ—… μŠ€ν¬λ¦½νŠΈμ™€ 배포 슀크립트λ₯Ό μ‹€ν–‰μ‹œν‚΅λ‹ˆλ‹€. 이 λ•Œ 블루 μ„œλ²„ ν¬νŠΈμ™€ κ·Έλ¦° μ„œλ²„ 포트λ₯Ό νŒŒλΌλ―Έν„°λ‘œ ν•¨κ»˜ 쀄 수 μžˆμŠ΅λ‹ˆλ‹€.
β—¦
μ²˜μŒμ—λŠ” 1>/dev/null 2>/dev/null 을 μ§€μ •ν•˜μ§€ μ•Šμ•„ ν•΄λ‹Ή 슀크립트λ₯Ό μ‹€ν–‰μ‹œμΌ°μ„ λ•Œ output 을 λ°›κΈ° μœ„ν•΄ λ¬΄ν•œμ • λŒ€κΈ°ν•˜λŠ” μ—λŸ¬κ°€ λ°œμƒν–ˆμ—ˆμŠ΅λ‹ˆλ‹€.
β€’
for retry in $(seq 10)
β—¦
10 번 λŒλ©΄μ„œ μƒˆλ‘œ μ‹€ν–‰ν•œ μ„œλ²„κ°€ 잘 μ‹€ν–‰λ˜μ—ˆλŠ”μ§€ 5초 κ°„κ²©μœΌλ‘œ ν™•μΈν•©λ‹ˆλ‹€.
β€’
for index in $(seq ${SIZE})
β—¦
각각의 μ„œλ²„μ—μ„œ μƒˆλ‘œ μ‹€ν–‰μ‹œν‚¨ κ·Έλ¦° μ„œλ²„μ˜ 포트λ₯Ό echo ν•©λ‹ˆλ‹€. echo λ₯Ό 톡해 좜λ ₯을 λ§Œλ“€κ³  이 좜λ ₯물을 λ‹€μŒ μŠ€ν…Œμ΄μ§€μ—μ„œ λΌμš°νŒ…ν•  λ•Œ μ‚¬μš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
stage('route') { try { echo "URL_A=${OUTPUT[0]}" echo "URL_B=${OUTPUT[1]}" sh 'ssh -i {PEM_KEY} ubuntu@${WS_IP} "bash {ROUTE_SCRIPT} ' + OUTPUT[0] + ' ' + OUTPUT[1] + '"' slackSend (channel: "jenkins", color: "good", message: "Route Succeeded - ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)") } catch(e) { slackSend (channel: "jenkins", color: "warning", message: "Route Failed - ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)") throw e } }
Groovy
볡사
route stage μ—μ„œλŠ” nginx κ°€ μžˆλŠ” μ„œλ²„μ— λΌμš°νŒ… λ³€κ²½ 슀크립트λ₯Ό μ‹€ν–‰μ‹œν‚΅λ‹ˆλ‹€. 이와 ν•¨κ»˜ κ·Έλ¦° μ„œλ²„μ˜ 포트λ₯Ό 인자둜 μ „λ‹¬ν•©λ‹ˆλ‹€.
stage('kill') { try { sub_list = [] for (res in OUTPUT) { sub_list.add(res) } sub_list = sub_list[(int) (sub_list.size() / 2)..sub_list.size() - 1] println sub_list PRD_KILL_WAS_IP_LIST = PRD_WAS_IP_LIST.split(" ") PRD_KILL_WAS_IP_LIST.eachWithIndex { item, index -> sh 'ssh -i PEM_KEY ubuntu@' + item + ' "bash KILL_SCRIPT ' + sub_list[index] + ' 1>/dev/null 2>/dev/null"' } slackSend (channel: "jenkins", color: "good", message: "Down Succeeded - ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)") } catch(e) { slackSend (channel: "jenkins", color: "warning", message: "Down Failed - ${env.JOB_NAME} ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)") throw e } }
Groovy
볡사
kill stageμ—μ„œλŠ” ꡬ λ²„μ „μ˜ μ„œλ²„λ₯Ό λ‚΄λ¦½λ‹ˆλ‹€. μ—¬κΈ°μ„œλŠ” groovy 문법을 λ‹€μ†Œ 많이 ν™œμš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€. 이 κ³Όμ •μ—μ„œ jenkins ν™˜κ²½ λ³€μˆ˜λ₯Ό λ°›μ•„μ˜€λ €λ©΄ ${} ν˜•νƒœκ°€ μ•„λ‹Œ PRD_WAS_IP_LIST λ³€μˆ˜λͺ…을 κ·ΈλŒ€λ‘œ μ‚¬μš©ν•΄μ•Ό groovy 의 νƒ€μž…μœΌλ‘œ λ°›μ•„μ˜¬ 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. μ—¬κΈ°μ„œ μ΅μˆ™μ§€ μ•Šμ€ groovy 문법과 μ—λŸ¬ λ‚΄μš©λ•Œλ¬Έμ— μ• λ₯Ό 많이 λ¨Ήμ—ˆμŠ΅λ‹ˆλ‹€

WAS 슀크립트 μ„€μ •

β€’
backup 슀크립트
cp {jar file μœ„μΉ˜} /home/ubuntu/deploy/backup/
Bash
볡사
λ°±μ—… μŠ€ν¬λ¦½νŠΈμ—μ„œλŠ” 기쑴의 jar νŒŒμΌμ„ backup 폴더에 λ³΅μ‚¬ν•©λ‹ˆλ‹€.
β€’
deploy 슀크립트
PORT=$1 pid=$(lsof -i:${PORT} | sed -n 2p | sed -e "s/ \{1,\}/ /g" | cut -d ' ' -f 2) if [ -n"${pid}" ] then kill -15 ${pid} echo kill process ${pid} else echo no process fi echo 'sleep..' sleep 30 echo 'now start' nohup java -jar -Dserver.port=${PORT} {jar file μœ„μΉ˜} >> {log file μœ„μΉ˜} & echo 'completed'
Bash
볡사
μœ„ jenkins νŒŒμ΄ν”„λΌμΈμ˜ deploy stageμ—μ„œ μ‹€ν–‰μ‹œν‚€λŠ” deploy μŠ€ν¬λ¦½νŠΈμž…λ‹ˆλ‹€. jenkins μ—μ„œ μœ„ 슀크립트 μˆ˜ν–‰ν•  λ•Œ 받은 포트 인자λ₯Ό PORT λ³€μˆ˜μ— μ €μž₯ν•©λ‹ˆλ‹€. 그리고 ν˜Ήμ‹œ ν•΄λ‹Ή ν¬νŠΈμ— ν”„λ‘œκ·Έλž¨μ΄ μ‹€ν–‰λ˜κ³  μžˆλ‹€λ©΄ -15 둜 μ’…λ£Œμ‹œν‚΅λ‹ˆλ‹€. 이후 nohup κ³Ό java λͺ…λ Ήμ–΄λ‘œ jar νŒŒμΌμ„ μ‹€ν–‰μ‹œν‚΅λ‹ˆλ‹€.
β€’
kill 슀크립트
PORT=$1 pid=$(lsof -i:${PORT} | sed -n 2p | sed -e "s/ \{1,\}/ /g" | cut -d ' ' -f 2) if [ -n"${pid}" ]; then kill -15 ${pid} echo kill process ${pid} echo 'kill 성곡' else echo no process fi
Bash
볡사
μ—¬κΈ°μ„œλ„ jenkins μ—μ„œ ν•΄λ‹Ή 슀크립트 μˆ˜ν–‰μ‹œ 인자둜 받은 port λ₯Ό ν™œμš©ν•©λ‹ˆλ‹€. 이 port 둜 기쑴의 μ•± μ„œλ²„ pid λ₯Ό μ°Ύκ³  μ’…λ£Œμ‹œν‚΅λ‹ˆλ‹€.
이후 nginx λ₯Ό μ΄μš©ν•œ λ‘œλ“œ λ°ΈλŸ°μ‹± μž‘μ—…μ€ λ‹€μŒ ν¬μŠ€νŒ…μ—μ„œ μ΄μ–΄κ°€κ² μŠ΅λ‹ˆλ‹€.