.entry .entry-content .table-of-contents > li > ul { display: none; } ã¯ããã« ããã«ã¡ã¯ãECåºç€éçºæ¬éšSREéšã®éç°ã è±æ¿ ãæŸç³ã§ããæ®æ®µã¯SREãšããŠZOZOTOWNã®ã€ã³ãã©éçšãéçºãæ
åœããŠããŸãã ZOZOã§ã¯ gatling-operator ãOSSã®è² è·è©ŠéšããŒã«ãšããŠå
¬éã»éçšããŠããŸãããããããGatlingæ¬äœã®ç Žå£ç倿Žãã¡ã³ãããŒäžè¶³ãšãã£ã課é¡ã«çŽé¢ããæ°ããªè² è·è©ŠéšããŒã«ãšã㊠k6 ã®å°å
¥ãé²ããŠããŸãã æ¬èšäºã§ã¯ãgatling-operatorãæ±ããŠãã課é¡ãšãk6ãžã®ç§»è¡ã«è³ã£ãçµç·¯ããããŠClaude CodeãæŽ»çšããæ¢åã·ããªãªã®ç§»è¡æ¹æ³ã«ã€ããŠã玹ä»ããŸãã ç®æ¬¡ ã¯ããã« ç®æ¬¡ èæ¯ã»èª²é¡ gatling-operatorãšã¯ Gatlingã®ç Žå£çå€æŽ Gatling 3.11ã§ã®å€æŽ Gatling 3.12ã§ã®å€æŽ ã¡ã³ãããŒäžè¶³ æ€èšãã察å¿ç ãªãk6ãæ¡çšããã®ã èŠä»¶ k6ãæ¡çšããçç± å°å
¥ã«ããã£ãŠã®èª²é¡ãšå·¥å€« å
±éã©ã€ãã©ãªã®å®è£
Gatlingã®feederæ©èœãšã¯ k6æšæºæ©èœã®èª²é¡ feederã©ã€ãã©ãªã®å®è£
ã·ããªãªåç
§æ¹æ³ã®æ€èš æ¡çšããã¢ãããŒãïŒSharedVolume & LocalFile Git飿ºã«ããã·ããªãªç®¡ç ãã£ã¬ã¯ããªæ§æ initContainerã«ããã·ããªãªååŸ GitHubèªèšŒ Datadogã«ãã詊éšçµæã®å¯èŠå k6æšæºã®ã¬ããŒãæ©èœã®èª²é¡ Datadogããã·ã¥ããŒãã®æ¡çš Datadogãžã®ã¡ããªã¯ã¹éä¿¡ OpenTelemetryæ¹åŒã§ã®éä¿¡ ã¡ããªã¯ã¹ãžã®ã¿ã°ä»ã ããã·ã¥ããŒãã®æ§æãšæ¡åŒµæ§ ClaudeãµããšãŒãžã§ã³ãã䜿ã£ãã·ããªãªç§»è¡ ClaudeãµããšãŒãžã§ã³ããšã¯ ã·ããªãªç§»è¡ã§ã®æŽ»ç𿹿³ ã©ã®ããã«å®çŸããŠããã ç§»è¡çµæãšå¹æ ãµããšãŒãžã§ã³ãã䜿ãäžã§ã®ãã€ã³ã ãããã« èæ¯ã»èª²é¡ gatling-operatorãšã¯ gatling-operator ã¯ãZOZOãOSSãšããŠå
¬éããŠããè² è·è©ŠéšããŒã«ã§ãã Gatling ãKubernetesç°å¢ã§å®è¡ããããã®Kubernetes OperatorãšããŠå®è£
ãããŠãããåæ£è² è·è©Šéšã®å®è¡ãã¬ããŒãã®èªåçæãªã©ã®æ©èœãæäŸããŠããŸãã Gatlingã®ç Žå£çå€æŽ gatling-operatorã®éçšãç¶ããäžã§ãGatlingæ¬äœã®ããŒãžã§ã³ã¢ããã«äŒŽãç Žå£ç倿Žã倧ããªèª²é¡ãšãªããŸããã Gatling 3.11ã§ã®å€æŽ Gatling 3.11ã§ã¯ããããŸã§æäŸãããŠãã gatling.sh ãšããGatlingãå®è¡ããããã®ã·ã§ã«ã¹ã¯ãªãããåé€ãããŸããã代ããã«ãMavenãGradleãšãã£ãJavaã®ãã«ãããŒã«ã䜿çšããããšãå¿
é ãšãªã£ãŠããŸãã gatling-operator㯠gatling.sh ãå®è¡ããããšãåæã«å®è£
ãããŠããããããã®å€æŽã«å¯Ÿå¿ããã«ã¯å€§å¹
ãªä¿®æ£ãå¿
èŠãšãªããŸããããŸããã·ããªãªããªãœãŒã¹ãã¡ã€ã«ãé
眮ãããã£ã¬ã¯ããªæ§æãããã«ãããŒã«ã䜿ã£ãå®è¡ã§ã¯åºæ¬çã«åºå®ãããããã«ãªããåŸæ¹äºæã®åé¡ãçããŸãã Gatling 3.12ã§ã®å€æŽ Gatling 3.12ã§ã¯ãè€æ°ã®ãã°ãã¡ã€ã«ããŸãšããŠã¬ããŒããäœæããæ©èœãåé€ãããŸãããgatling-operatorã¯åæ£å®è¡ããåPodã®ãã°ãã¡ã€ã«ãéçŽããŠã¬ããŒããçæããä»çµã¿ãæ¡çšããŠããããããã®å€æŽã¯èŽåœçãªåœ±é¿ãäžããŸãã ãªãããã®æ©èœã¯Enterpriseçã§ã¯åŒãç¶ãå©çšå¯èœãšã®ããšã§ããããããgatling-operatorã¯OSSçã®Gatlingãåæã«èšèšãããŠãããEnterpriseçãå©çšããèšèšãžã®å€æŽã¯å°é£ã§ãã ã¡ã³ãããŒäžè¶³ gatling-operatorã®ã¡ã³ãããŒã¯çŸç¶2åäœå¶ã§éçšããŠããŸããGatlingã®ç Žå£ç倿Žã«è¿œéããããã®æ¹ä¿®å·¥æ°ãèãããšããã®äœå¶ã§ã¯ååãªå¯Ÿå¿ãå°é£ãªç¶æ³ã§ããã æ€èšãã察å¿ç ãããã®èª²é¡ã«å¯ŸããŠã以äžã®3ã€ã®å¯Ÿå¿çãæ€èšããŸããã 察å¿ç ã¡ãªãã ãã¡ãªãã gatling-operatorã®æ¹ä¿® æ¢åã·ããªãªè³ç£ããã®ãŸãŸäœ¿ãã æ¹ä¿®èŠæš¡ã倧ãããå°æ¥æ§ã«æžå¿µ Gatling Enterpriseçãžã®ç§»è¡ ã¡ã³ããã³ã¹ã³ã¹ãåæž ééçãªè¿œå ã³ã¹ããçºç ä»è² è·è©ŠéšããŒã«ãžã®ç§»è¡ ã¡ã³ããã³ã¹ã³ã¹ãåæžãOSSãªã远å ã³ã¹ããªã æ¢åã·ããªãªè³ç£ã®ç§»è¡ãå¿
èŠ æ€èšã®çµæãOSSãšããŠåºãå©çšãããŠãããKubernetesç°å¢ã§ã®å®è¡ããµããŒããããŠãã k6 ãžã®ç§»è¡ãéžæããŸãããæ¢åã·ããªãªã®ç§»è¡ã«ã€ããŠã¯ãçæAIãæŽ»çšããããšã§å·¥æ°ãåæžã§ãããšå€æããŸããã ãªãk6ãæ¡çšããã®ã èŠä»¶ gatling-operatorã®ã¡ã³ãããŒãäžåšãªããšãè²æã³ã¹ãã®é«ããããæ°ããªè² è·è©ŠéšããŒã«ã暡玢ããŸãããOSSãšããŠã®ã³ãã¥ããã£ã®å€§ãããKubernetesã§éçšããæ¢ååºç€ã«å°å
¥ã§ããåœ¢ãæ¡ä»¶ã§ããGatlingããä»ã®è² è·è©ŠéšããŒã«ã«ç§»è¡ããã«ããã£ãŠã以äžã®3ã€ã®èŠä»¶ããããŸããã ã¡ã³ããããªãã£ã®é«ã : ãªãã¹ãã¡ã³ããã³ã¹ã³ã¹ããæããããã«ãèªç€Ÿã§ç¬èªã®ããŒã«ãäœæããªããŠãè¯ãOSSã®æ¡çšãæ€èšã è² è·è©Šéšçµæãã¬ããŒããšããŠæ®ããã : Gatlingãçšããè² è·è©Šéšã§ã¯ãGatlingã¬ããŒããåºåããããããZOZOã§ã¯ãšããã³ã¹ãšããŠãã®ã¬ããŒãã䜿çšããŠããããã®ãããç§»è¡å
ã®ããŒã«ã§ãçµæãèŠããããšããã®ã¯éèŠã ã·ããªãªäœæã®å®¹æã : ZOZOã§ã¯ãèŠä»¶ã«ãããããã¯ãšã³ããšSREãã·ããªãªãäœæããããã®ãããã·ããªãªäœæã®éã«åŠç¿ã³ã¹ããã¬ãã¥ãŒã³ã¹ãããªãã¹ãäœãèšèªãæ¡çšããå¿
èŠãããã k6ãæ¡çšããçç± ç§»è¡å
ãæ€èšããéã以äžã®ããŒã«ãåè£ã«äžãããŸããããåŸè¿°ããçç±ããk6ãžã®å°å
¥ã決ãŸããŸããã Locust k6 ZOZOã§ã¯ãäºæ¥éšåäœã§SREãååšããŠãããè² è·è©Šéšåºç€ãæã£ãŠããŸãããã®1ã€ãšããŠWEARããŒã ã®è² è·è©Šéšåºç€ã§k6ã®å°å
¥å®çžŸããã£ãããã瀟å
ãã¬ããžã掻çšã§ããããšã¯å€§ããªã¡ãªããã§ããã techblog.zozo.com ãŸããk6ã¯Grafana補ã®OSSã§ãããKubernetesäžã§k6ãåäœãããããã® k6-operator ããã§ã«ãããŸããããã« xk6 ã䜿çšããããšã§ãã«ã¹ã¿ã ãã«ããç°¡åã«æ¡åŒµæ©èœãªã©ãå°å
¥ã§ããŸããk6-operatorãxk6ãªã©ãGrafana瀟ãäœæããŠããå
¬åŒã®OSSã§ãããã¡ã³ããã³ã¹ãèªç€Ÿã§æ
ããªããŠè¯ãã®ã¯å€§ããªã¡ãªããã§ãããk6-operatorã¯CRDãå°å
¥ããŠããããããã§ã¹ããã¡ã€ã«ãé©çšããã ãã§è² è·è©Šéšãç°¡åã«å®è¡ã§ããæ¢åã®gatling-operatorãšå€ãããã«éçšã§ããç¹ãé
åã§ãã ããã«ãã·ããªãªäœæãããéã«JavaScriptã§èšè¿°ã§ããã®ãã¡ãªããã§ãããè² è·è©Šéšã·ããªãªã§ã¯ãè€éãªåŠçã¯å®è£
ããªããããã¬ãã¥ãŒãåŠç¿ã®ã³ã¹ããæããããç¹ã¯é
åçã§ããã 以äžã®çç±ãããæçµçã«ã¯k6ãPoCçã«å°å
¥ããããšã決å®ããŸããããŸãå®éã«k6ãæ€èšŒããäžã§ãè¶³ããªãæ©èœãªã©ã¯å¥éãæ€èšŒã»å®è£
ã»è°è«ããã€ã€å°å
¥ããŠãããŸããã å°å
¥ã«ããã£ãŠã®èª²é¡ãšå·¥å€« å
±éã©ã€ãã©ãªã®å®è£
k6ãžã®ç§»è¡ãé²ããäžã§ãGatlingã«ã¯ååšãããã®ã®ãk6ã«ã¯æšæºã§çšæãããŠããªãæ©èœãããããšã«æ°ã¥ããŸãããç¹ã«åé¡ãšãªã£ãã®ã feeder æ©èœã§ãã Gatlingã®feederæ©èœãšã¯ Gatlingã®feederã¯ãCSVãã¡ã€ã«ãªã©ã®å€éšããŒã¿ãœãŒã¹ãããã¹ãããŒã¿ãèªã¿èŸŒã¿ãåä»®æ³ãŠãŒã¶ãŒã«é 次ãŸãã¯åŸªç°çã«ããŒã¿ãäŸçµŠããä»çµã¿ã§ããäŸãã°ããã°ã€ã³ã·ããªãªã§è€æ°ã®ãŠãŒã¶ãŒèªèšŒæ
å ±ã䜿ãåãå Žåãªã©ã«éå®ããŸãã // Gatlingã§ã®feeder䜿çšäŸ val feeder = csv( "users.csv" ).circular val scn = scenario( "Login" ) .feed(feeder) .exec(http( "login" ).post( "/auth" ).body(StringBody( """{" email ":" ${email} "," password ":" ${password} "}""" ))) ZOZOã®è² è·è©Šéšã·ããªãªã§ã¯ããã®feederæ©èœãå€ãã®ã·ããªãªã§äœ¿çšãããŠããŸããããŠãŒã¶ãŒèªèšŒæ
å ±ãååIDãã»ãã·ã§ã³æ
å ±ãªã©ãæ§ã
ãªãã¹ãããŒã¿ãfeederã§ç®¡çããŠãããããk6ãžã®ç§»è¡ã«ããã£ãŠåçã®æ©èœãå¿
é ã§ããã k6æšæºæ©èœã®èª²é¡ k6ã«ã¯CSVèªã¿èŸŒã¿æ©èœã¯ãããã®ã®ãGatlingã®feederã®ãããªé 次äŸçµŠã»åŸªç°å©çšã®æ©èœã¯æšæºã§æäŸãããŠããŸããã // k6æšæºã®ããŒã¿èªã¿èŸŒã¿ïŒfeederã®ãããªæ©èœã¯ãªãïŒ import { SharedArray } from 'k6/data' ; const data = new SharedArray ( 'users' , function () { return JSON . parse ( open ( './users.json' )) ; }) ; ãã®æ¹æ³ã§ã¯ãã©ã®VUãã©ã®ããŒã¿ã䜿çšãããã®å¶åŸ¡ããããŒã¿ã®åŸªç°äŸçµŠãèªåã§å®è£
ããå¿
èŠããããŸããã feederã©ã€ãã©ãªã®å®è£
ããã§ãGatlingã®feederæ©èœãåçŸããå
±éã©ã€ãã©ãªãå®è£
ããŸããããã®ã©ã€ãã©ãªã¯ k6_scenarios/common_lib/feeder/ ãã£ã¬ã¯ããªã«é
眮ããã以äžã®æ©èœãæäŸããŸãã // feederã©ã€ãã©ãªã®äœ¿çšäŸ import { csv } from '../common_lib/feeder/feeder.js' ; // CSVããŒã¿ã埪ç°çã«äŸçµŠããfeederãäœæ const userFeeder = csv ( open ( './users.csv' )) . circular () ; export default function () { // åVUã®å®è¡ããšã«æ¬¡ã®ããŒã¿ãååŸ const userData = userFeeder . next () ; http . post ( ` ${ ENDPOINT } /auth/login` , JSON . stringify ({ email : userData . email , password : userData . password })) ; } å®è£
ããfeederã©ã€ãã©ãªã¯ã以äžã®äŸçµŠæŠç¥ããµããŒãããŠããŸãã äŸçµŠæŠç¥ 説æ Gatlingçžåœ circular() ããŒã¿ã埪ç°çã«äŸçµŠïŒæ«å°Ÿã«éãããå
é ã«æ»ãïŒ csv().circular random() ã©ã³ãã ã«ããŒã¿ãéžæ csv().random queue() é æ¬¡äŸçµŠïŒããŒã¿ãå°œããããšã©ãŒïŒ csv().queue ãŸããé
åããŒã¿ãéã¿ä»ãããŒã¿ã«ã察å¿ããŠããŸãã import { csv , array , weighted } from '../common_lib/feeder/feeder.js' ; // é
åããfeederãäœæ const itemFeeder = array ([ 'item1' , 'item2' , 'item3' ]) . circular () ; // éã¿ä»ãã§ããŒã¿ãéžæ const scenarioFeeder = weighted ([ { weight : 70 , data : { type : 'browse' } } , { weight : 20 , data : { type : 'search' } } , { weight : 10 , data : { type : 'purchase' } } ]) ; ãã®ã©ã€ãã©ãªãå®è£
ããããšã§ãæ¢åã®Gatlingã·ããªãªã§äœ¿çšããŠããfeederæ©èœãk6ã§ãåæ§ã«å©çšã§ããããã«ãªããŸãããã·ããªãªç§»è¡ã®éããfeederã®äœ¿çšãã¿ãŒã³ã倿ã«ãŒã«ãšããŠå®çŸ©ããããšã§ãã¹ã ãŒãºãªç§»è¡ãå¯èœã«ãªã£ãŠããŸãã ã·ããªãªåç
§æ¹æ³ã®æ€èš k6-operatorã§ã¯ãã·ããªãªãã¡ã€ã«ãRunner Podã«ããŠã³ãããæ¹æ³ãšããŠä»¥äžã®3ã€ãæäŸãããŠããŸãã æ¹æ³ æŠèŠ ã¡ãªãã ãã¡ãªãã ConfigMap ã·ããªãªãConfigMapãšããŠç»é² ã·ã³ãã«ã§æè»œ 容éå¶éããïŒçŽ1MBïŒãè€æ°ãã¡ã€ã«äžåã VolumeClaim PVCã«ã·ããªãªãé
眮 ãã¡ã€ã«ãµã€ãºå¶éãªããè€æ°ãã¡ã€ã«å¯Ÿå¿ æ§æãè€é LocalFile ã«ã¹ã¿ã ã€ã¡ãŒãžã«ã·ããªãªãå
å
åçŸæ§ãé«ããå€éšäŸåãªã 軜埮ãªä¿®æ£ã§ãåãã«ããå¿
èŠ gatling-operatorã§ã¯ãã·ããªãªãå«ãã ã³ã³ããã€ã¡ãŒãžããã«ãããŠå®è¡ããæ¹åŒãæ¡çšããŠããŸãããããã¯LocalFileã«çžåœããæ¹æ³ã§ããããããã·ããªãªãä¿®æ£ãããã³ã«ã³ã³ããã€ã¡ãŒãžã®ãã«ããšããã·ã¥ãå¿
èŠãšãªããéçºãµã€ã¯ã«ãé
ããªããã¡ãšãã課é¡ããããŸããã k6ãžã®ç§»è¡ã«ãããããã®éçºãµã€ã¯ã«ãæ¹åããããšããçãããããã·ããªãªåç
§æ¹æ³ãèŠçŽãããšã«ããŸããã æ¡çšããã¢ãããŒãïŒSharedVolume & LocalFile æ€èšã®çµæãå
¬åŒããã¥ã¡ã³ãã§ç޹ä»ãããŠãã3ã€ã®æ¹æ³ã§ã¯ãªããVolumeClaimãšLocalFileã®ãã€ããªãããªã¢ãããŒããæ¡çšããŸããã ãã®ã¢ãããŒãã§ã¯ãPVCïŒPersistentVolumeClaimïŒã¯äœ¿çšãããPodã®å
±æããªã¥ãŒã ïŒemptyDirïŒãšinitContainerãçµã¿åãããŸããinitContainerã§Gitãªããžããªããã·ããªãªãã¡ã€ã«ãã¯ããŒã³ããRunner Containerãå
±æããªã¥ãŒã çµç±ã§ãã®ãã¡ã€ã«ãåç
§ããä»çµã¿ã§ãã ãã®ã¢ãããŒãã«ã¯ä»¥äžã®ã¡ãªããããããŸãã PVCäžèŠã§ã·ã³ãã« : VolumeClaimã®ãããªæ°žç¶ããªã¥ãŒã ã®ç®¡çãäžèŠã§ããããã§ã¹ã管çãã·ã¹ãã æ§æãã·ã³ãã«ã«ãªã èªåã¯ãªãŒã³ã¢ãã : å
±æããªã¥ãŒã ã¯Podå逿ã«ããŒã¿ãåé€ããããããåŸçä»ããäžèŠ éçºãµã€ã¯ã«ã®æ¹å : ã·ããªãªã®ä¿®æ£ã¯ã³ãããïŒããã·ã¥ããã ãã§åæ ããããããã³ã³ããã€ã¡ãŒãžã®ãã«ããäžèŠã«ãªã ãã©ã³ãæå®ã«ããæè»æ§ : éçºäžã®ã·ããªãªãéçºãã©ã³ããæå®ããããšã§ãããã«è©Šéšå¯èœ äžæ¹ã§ãparallelismãèšå®ããŠRunner Podãè€æ°ã«ãªãå Žåã¯ãPodåã ãGit cloneãå®è¡ãããç¹ã«ã¯æ³šæãå¿
èŠã§ãããã ããsparse-checkoutã䜿çšããŠå¿
èŠãªãã£ã¬ã¯ããªã®ã¿ãã¯ããŒã³ããããšã§ããã®åœ±é¿ãæå°éã«æããŠããŸãã Git飿ºã«ããã·ããªãªç®¡ç k6ã®ã·ããªãªãã¡ã€ã«ã¯ãæ¢åã®gatlingçšã·ããªãªãšåæ§ã«GitHubãªããžããªã§ç®¡çããŠããŸããTestRunãªãœãŒã¹ã®initContainerã§ git-sync ã䜿çšãããã¹ãå®è¡æã«ãªããžããªããã·ããªãªãã¡ã€ã«ãã¯ããŒã³ããä»çµã¿ãå®è£
ããŸããã ãã£ã¬ã¯ããªæ§æ zozo-benchmark/ âââ k6_scenarios/ âââ common_lib/ # å
±éã©ã€ãã©ãª â âââ feeder/ # ãã¹ãããŒã¿èªã¿èŸŒã¿çš â âââ metrics/ # ã¡ããªã¯ã¹åŠççš âââ sample/ # ã·ããªãªãã£ã¬ã¯ã㪠â âââ testrun.yaml # TestRunãããã§ã¹ã â âââ test.js # ã·ããªãªãã¡ã€ã« âââ resources/ # ãã¹ãããŒã¿ âââ sample/ âââ data.csv initContainerã«ããã·ããªãªååŸ TestRunãªãœãŒã¹ã§ã¯ããã¹ãå®è¡åã«initContainerã2段éã®åŠçãè¡ããŸãã prepare-sparse-checkout : sparse-checkoutã®ãã¿ãŒã³ãã¡ã€ã«ãäœæããå¿
èŠãªãã£ã¬ã¯ããªã®ã¿ãã¯ããŒã³å¯Ÿè±¡ãšããŠæå® clone-data : git-syncã䜿çšããŠGitHubãªããžããªããæå®ãã©ã³ãã®ã·ããªãªãã¡ã€ã«ãã¯ããŒã³ apiVersion : k6.io/v1alpha1 kind : TestRun metadata : name : k6-sample spec : parallelism : 2 arguments : --out experimental-opentelemetry runner : volumes : - name : git-sync-config emptyDir : {} - name : scenario-data emptyDir : {} volumeMounts : - name : scenario-data mountPath : /test initContainers : - name : prepare-sparse-checkout image : busybox:1.36 command : [ "/bin/sh" , "-c" ] env : - name : TARGET_SCENARIO value : scenario_samples/simple args : - | cat <<EOF > /git-sync-config/sparse-checkout-patterns k6_scenarios/common_lib/ k6_scenarios/resources/ k6_scenarios/${TARGET_SCENARIO}/ EOF volumeMounts : - name : git-sync-config mountPath : /git-sync-config - name : clone-data image : registry.k8s.io/git-sync/git-sync:v4.4.2 args : - --repo=https://<ã·ããªãªæ ŒçŽãªããžããª>.git - --link=zozo-benchmark - --depth=1 - --root=/test - --one-time - --sparse-checkout-file=/git-sync-config/sparse-checkout-patterns envFrom : - secretRef : name : k6-github-app-secrets-xxxx env : - name : GITSYNC_REF value : main # ã¯ããŒã³ãããã©ã³ããæå® volumeMounts : - name : git-sync-config mountPath : /git-sync-config readOnly : true - name : scenario-data mountPath : /test script : localFile : /test/zozo-benchmark/k6_scenarios/scenario_samples/simple/test.js ãã®ä»çµã¿ã«ãããã·ããªãªãã¡ã€ã«ã®ä¿®æ£ãã³ãããã»ããã·ã¥ããã ãã§ã次åã®è©Šéšå®è¡æã«ææ°ã®ã·ããªãªãåæ ãããŸãã GITSYNC_REF ã§ãã©ã³ããæå®ã§ãããããéçºäžã®ã·ããªãªãéçºãã©ã³ããæå®ããããšã§ããã«è©Šéšã§ããŸãã GitHubèªèšŒ ãã©ã€ããŒããªããžããªããã®ã¯ããŒã³ã«ã¯ãGitHub Appsã«ããèªèšŒã䜿çšããŠããŸãã èªèšŒæ
å ±ã®ç®¡çã«ã¯ãAWS Secrets ManagerãšExternal Secretsãçµã¿åãããŠããŸããGitHub Appsã®èªèšŒæ
å ±ãSecrets Managerã«ä¿åããExternal Secretsçµç±ã§Kubernetes SecretãšããŠèªã¿èŸŒã¿ãŸãã apiVersion : external-secrets.io/v1beta1 kind : ExternalSecret metadata : name : k6-github-app-secrets-xxxx spec : refreshInterval : "1h" secretStoreRef : name : cluster-secretstore kind : ClusterSecretStore data : - secretKey : GITSYNC_GIT_CREDENTIAL remoteRef : key : k6/github-app - secretKey : GITSYNC_GIT_CREDENTIAL_PRIVATEKEY remoteRef : key : k6/github-app-privatekey clone-dataã³ã³ããã§ã¯ããã®Secretã envFrom ã§åç
§ããŠããŸãã - name : clone-data image : registry.k8s.io/git-sync/git-sync:v4.4.2 envFrom : - secretRef : name : k6-github-app-secrets-xxxx git-syncã¯ãããã®ç°å¢å€æ°ãèªåçã«èªã¿åããGitHub Appsã§èªèšŒããŠãã©ã€ããŒããªããžããªãžã®ã¢ã¯ã»ã¹ãå®çŸããŠããŸãã Datadogã«ãã詊éšçµæã®å¯èŠå Gatlingããk6ãžã®ç§»è¡ã«ãããã詊éšçµæãå¯èŠåã»ä¿åããæ¹æ³ã«ã€ããŠãæ€èšããŸãããè² è·è©Šéšåºç€ãå©çšãããšã³ãžãã¢ã®ç§»è¡ããŒãã«ãäžããªãããããŸããå©çšäœéšãæãªããã«è©ŠéšçµæããããŸã§éããšããã³ã¹ãšããŠæŽ»çšã§ããããã以äžã®èŠä»¶ãæºããå¿
èŠããããŸããã Gatlingã¬ããŒããšåçã®ã¡ããªã¯ã¹ãçµ±èšæ
å ±ãå¯èŠåã§ããã㚠詊éšçµæãäžå®ã®æéä¿åããåŸãã確èªã»æ¯èŒã§ããããš æ€èšã®çµæãk6ã®ã¡ããªã¯ã¹ãDatadogã«éä¿¡ããç¬èªã®ããã·ã¥ããŒãã§è©Šéšçµæãå¯èŠåããæ¹åŒãæ¡çšããŸããã以éã§ã¯ãDatadogããã·ã¥ããŒããæ¡çšããçç±ãšæ§ç¯æ¹æ³ã«ã€ããŠèª¬æããŸãã k6æšæºã®ã¬ããŒãæ©èœã®èª²é¡ gatling-operatorã§ã¯ãGatlingã«ããåºåãããHTMLã¬ããŒããAWS S3ã«ä¿åããæ©èœãååšããå©çšè
ã¯S3äžã®ãã¡ã€ã«ã«ã¢ã¯ã»ã¹ããããšã§è©Šéšçµæã確èªã§ããŸãããk6ã§ãåæ§ã®æ¹åŒã§è©Šéšçµæãä¿åã»ç¢ºèªã§ããªããæ€èšããŸããã k6ã«ã¯HTMLã¬ããŒããåºåããæšæºæ©èœã§ãã handleSummary 颿°ãããããªãããªHTMLã¬ããŒããåºåã§ãã benc-uk/k6-reporter ãååšããŸããããããã¯ã©ãŠãã¹ãã¬ãŒãžã«ã¬ããŒããä¿åããä»çµã¿ã¯ååšãããã³ã¹ãããããŠäœã蟌ãå¿
èŠããã£ãããããã®æ¹åŒã¯æ¡çšããŸããã§ããã JSON/CSVåºåæ©èœãå©çšããæ¡ãæ€èšããŸããããå¯èŠåã®äœã蟌ã¿ã«ã³ã¹ãããããããŸãäžèšãšåæ§ã«ã¬ããŒããä¿åããä»çµã¿ãæ§ç¯ããå¿
èŠããã£ãããããã¡ããæ¡çšããŸããã§ããã Datadogããã·ã¥ããŒãã®æ¡çš ä»ã®æ¡ãæ¯èŒæ€èšããŸããããæ®æ®µããç£èŠããŒã«ãšããŠDatadogã䜿çšããŠããããšãããk6ã®ã¡ããªã¯ã¹ãå©çšããç¬èªã®Datadogããã·ã¥ããŒããäœæããæ¹åŒãæçµçã«æ¡çšããŸããã Datadogãªã以äžã®ããã«ããããã®èŠä»¶ãæºãããŸããã ããã·ã¥ããŒãæ©èœã«ãã詊éšçµæãå¯èŠåã§ããããš Datadogã®ã¡ããªã¯ã¹ã¯é·æéä¿åãã 1 ãã¿ã°ä»ãã«ãã詊éšããšã®çµã蟌ã¿ãšæ¯èŒãå¯èœãªããš æ¢åã®Datadogç°å¢ã掻çšã§ããããã以äžã®ã¡ãªããããããŸããã äœãå°å
¥ã³ã¹ã ïŒè² è·è©Šéšåºç€ã«ã¯datadog-agentãå°å
¥æžã¿ã®ããk6ã®ã¡ããªã¯ã¹åéã容æ çµ±åçãªåæ ïŒDatadogäžã§è©Šéšçµæãšè©Šéšäžã®ãã¬ãŒã¹ãã«ã¹ã¿ã ã¡ããªã¯ã¹ãçŽä»ããŠç¢ºèªã§ãã å©çšè
ã®èªç¥è² è·ãåŠç¿ã³ã¹ãã®è»œæž ïŒäœ¿ãæ
£ããããã·ã¥ããŒãæ©èœã§æè»ã«è©Šéšçµæãå¯èŠåã§ãã Datadogãžã®ã¡ããªã¯ã¹éä¿¡ k6ã®è©ŠéšçµæãDatadogããã·ã¥ããŒãã§å¯èŠåããã«ã¯ãk6ã®ã¡ããªã¯ã¹ãDatadogã«éä¿¡ããå¿
èŠããããŸããDatadogãžã®ã¡ããªã¯ã¹éä¿¡ã«ã¯ãè² è·è©Šéšåºç€ã«å°å
¥æžã¿ã®datadog-agentãæŽ»çšããŸããããŸãã詊éšçµæãåºå¥ã§ããããã«ã¡ããªã¯ã¹ã«ã¿ã°ãä»äžãã工倫ãè¡ããŸããã OpenTelemetryæ¹åŒã§ã®éä¿¡ k6ããDatadogã«ã¡ããªã¯ã¹ãéä¿¡ããæ¹æ³ãšããŠã StatsDæ¹åŒ ãš OpenTelemetryæ¹åŒ ãååšããŸããStatsDæ¹åŒã¯æ¡åŒµæ©èœãå«ãã ã³ã³ããã€ã¡ãŒãžããã«ãããå¿
èŠããã£ããããKubernetesãããã§ã¹ããžã®èšå®è¿œå ã®ã¿ã§æžãOpenTelemetryæ¹åŒãæ¡çšããŸããã 以äžã®æé ã§k6ããDatadogãžã®ã¡ããªã¯ã¹éä¿¡ãå®çŸããŸããã å
¬åŒããã¥ã¡ã³ã ã«åŸãdatadog-agentã®OTLPã¡ããªã¯ã¹åä¿¡ãæå¹å k6ã®ã¡ããªã¯ã¹éä¿¡èšå®ãConfigMapãšããŠäœæ TestRunã«ConfigMapåç
§ãš --out experimental-opentelemetry åŒæ°ã远å ConfigMapãšTestRunèšå®äŸã¯ãããã以äžã®éãã§ãã apiVersion : v1 kind : ConfigMap metadata : name : k6-system-config data : K6_OTEL_EXPORTER_TYPE : "grpc" K6_OTEL_GRPC_EXPORTER_ENDPOINT : "datadog-agent.kube-support.svc.cluster.local:4317" K6_OTEL_SERVICE_NAME : "k6" K6_OTEL_METRIC_PREFIX : "k6_" K6_OTEL_GRPC_EXPORTER_INSECURE : "true" apiVersion : k6.io/v1alpha1 kind : TestRun metadata : name : k6-metrics-sample spec : arguments : --out experimental-opentelemetry runner : envFrom : - configMapRef : name : k6-system-config ã¡ããªã¯ã¹ãžã®ã¿ã°ä»ã Datadogããã·ã¥ããŒãã§è©Šéšçµæãåºå¥ããã«ã¯ã¡ããªã¯ã¹ã«ã¿ã°ãä»äžããŠããå¿
èŠããããŸããè² è·è©Šéšåºç€ã§çµ±äžçãªã¿ã°ãä»äžã§ããããã«ãå
±éã©ã€ãã©ãªãšã㊠performRequest ãšããHTTPãªã¯ãšã¹ãã®ã©ãããŒé¢æ°ãå®è£
ããŸããã 以äžãå®è£
ãã performRequest ã§ãã commonTags ã¯ããã·ã¥ããŒãã§ã®è©Šéšçµæã®çµã蟌ã¿ã«å©çšããå
±éã¿ã°ã§ãããŸãã詳现åæã§å©çšã§ãããªã¯ãšã¹ãåºæã®ã¿ã°ãä»äžããŠããŸãã import { check } from 'k6' ; import { commonTags } from './tags.js' ; export function performRequest ( requestName , requestFunc , expectedStatus = 200 ) { try { const estimatedPath = `/ ${ requestName } ` ; const preRequestTags = { ... commonTags , request_name : requestName , path : estimatedPath , endpoint : requestName } ; const response = requestFunc ( preRequestTags ) ; const actualPath = getPathFromUrl ( response . url ) || estimatedPath ; const statusCategory = getStatusCategory ( response . status ) ; const checkResult = check ( response , { [ ` ${ requestName } : status is ${ expectedStatus } ` ] : ( r ) => r . status === expectedStatus , [ ` ${ requestName } : response time < 5000ms` ] : ( r ) => r . timings . duration < 5000 , } , { ... commonTags , request_name : requestName , path : actualPath , status_code : response . status . toString () , status_category : statusCategory , endpoint : requestName }) ; const isSuccess = response . status === expectedStatus ; if ( ! isSuccess ) { console . error ( `â ${ requestName } : ${ response . status } - ${ actualPath } ` ) ; } return response ; } catch ( error ) { console . error ( ` ${ requestName } error: ${ error } ` ) ; throw error ; } } commonTags ã®å
容ã¯ä»¥äžã®éãã§ãã export const commonTags = { test_run_id : __ENV . K6_TEST_RUN_ID || console . warn ( 'K6_TEST_RUN_ID not set. Use environment variable for consistent ID across pods.' ) || `test-run- ${ new Date () . toISOString () . slice ( 0 , 16 ) . replace (/ [ T: ] / g , '-' )} ` , env : __ENV . K6_ENV || 'stg' , scenario : __ENV . K6_SCENARIO || 'default' , team : __ENV . K6_TEAM || 'default' , } ; performRequest ãå©çšããã«ã¯ä»¥äžã®2ã€ã®ã¹ããããå¿
èŠã§ãã TestRunã§ã®ç°å¢å€æ°ã®èšå® ã·ããªãªãã¡ã€ã«å
ã®ãªã¯ãšã¹ãåŠçã performRequest ã䜿çšããããã«æžãå€ãã TestRunã®ç°å¢å€æ°ãšã·ããªãªãã¡ã€ã«ã®èšè¿°äŸã¯ãããã以äžã®éãã§ãã env : - name : K6_TEST_RUN_ID value : "member-api-stress-test-20251209" - name : K6_ENV value : "stg" - name : K6_SCENARIO value : "member-api-create-and-update-user" - name : K6_TEAM value : "member-api-team" import { performRequest } from '../common_lib/metrics/metrics.js' ; import http from 'k6/http' ; export default function () { performRequest ( 'create_user' , ( tags ) => { return http . post ( 'https://api.example.com/users' , JSON . stringify ({ name : 'Test User' , email : 'test@example.com' }) , { headers : { 'Content-Type' : 'application/json' } , tags }) ; } , 200 ) ; performRequest ( 'update_user' , ( tags ) => { return http . put ( 'https://api.example.com/users/123' , JSON . stringify ({ name : 'Updated User' }) , { headers : { 'Content-Type' : 'application/json' } , tags }) ; } , 200 ) } ; ããã·ã¥ããŒãã®æ§æãšæ¡åŒµæ§ 以äžã®ç»åã¯ä»åäœæããDatadogããã·ã¥ããŒãã®ã¡ã€ã³éšåã§ãã 4ã€ã®ã¿ã°ã«ãã詊éšçµæãçµã蟌ãããšãã§ããŸãã ã¿ã°å 説æ test_run_id ãã¹ãå®è¡ã®èå¥å scenario ãã¹ãã·ããªãªå team å®è¡ããŒã å env ãã¹ã察象ç°å¢ åãŠã£ãžã§ããã¯Gatlingã¬ããŒãã®é
ç®ãåèã«ãã€ã€ãk6åºæã®ã¡ããªã¯ã¹ã確èªã§ããæ§æã«ããŸãããGatlingã¬ããŒããšåç以äžã®è©Šéšçµæã®å¯èŠåãå®çŸã§ãããšæããŸãã ããã«ãåŸæ¥ã®Gatlingã¬ããŒããåºå®åœ¢åŒã ã£ãã®ã«å¯ŸããDatadogããã·ã¥ããŒãã§ã¯ä»¥äžã®ãããªæè»ãªæ¡åŒµãå¯èœã§ãã ã«ã¹ã¿ã ã¡ããªã¯ã¹ãçµã¿åãããåæ è©Šéšå
容ãç®çã«å¿ããããã·ã¥ããŒãã®ã«ã¹ã¿ãã€ãº ããŒã ããšã®ç¬èªããã·ã¥ããŒãäœæãšå
±æ ClaudeãµããšãŒãžã§ã³ãã䜿ã£ãã·ããªãªç§»è¡ k6ãå°å
¥ããåŸã®èª²é¡ãšããŠãScalaã§èšè¿°ãããæ¢åã·ããªãªãã©ã®ããã«ç§»è¡ããŠãããããããŸãããGatlingã·ããªãªãçŽ130åããããããå
šãŠãæåã§k6çšã«ç§»è¡ããã®ã¯å°é£ã§ããã ããã«ãã·ããªãªç§»è¡ãå®çŸããã«ããã£ãŠãå
±éã®ã³ã³ããã¹ããçšããŠç§»è¡ããããšã¯å¿
é ã§ãããå
·äœçã«ã¯ä»¥äžã®ãããªã«ãŒã«ã«æºæ ããå¿
èŠããããŸããã feederæ©èœãDatadogã«ã¿ã°ä»ãã¡ããªã¯ã¹ãéä¿¡ããããã®å
±éã©ã€ãã©ãªã䜿çšããããš ã·ããªãªãGit飿ºã§äœ¿ããããã«ããäžæ¹ãç§å¿æ
å ±ïŒãã°ã€ã³ã»ãã¹ã¯ãŒããªã©ïŒãå«ãŸããCSVãã¡ã€ã«ã¯AWS Secrets ManagerããååŸããããš ãããã®èŠä»¶ãããClaudeãµããšãŒãžã§ã³ããçšããã·ããªãªç§»è¡ãæ€èšŒããŸããã ClaudeãµããšãŒãžã§ã³ããšã¯ Claude Codeã®æ©èœã§ãããå°éçãªã¿ã¹ã¯ã«çšããããšãã§ããã³ã³ããã¹ã管çã容æã«ããããšãå¯èœãšãªããŸãããã詳现ãªå
容ã¯ãå
¬åŒããã¥ã¡ã³ããã芧ãã ããã code.claude.com ã·ããªãªç§»è¡ã§ã®æŽ»ç𿹿³ ã·ããªãªç§»è¡ã«ããã£ãŠã以äžã®ãããªäœ¿ç𿹿³ãæ€èšŒããŸããããŸã claude ã³ãã³ãã§Claude Codeãèµ·åããŸãã $ claude Claude Codeã®èµ·ååŸããµããšãŒãžã§ã³ãã䜿çšããŠGatlingã·ããªãªãk6ã«ç§»è¡ãããæšã®æç€ºãéä¿¡ããŸãããã®åŸããµããšãŒãžã§ã³ãããã®è³ªåã«å¯ŸããŠåçããŸãã以äžã®å
·äœäŸã§ã¯ã 5. Type something. ã«å¯Ÿè±¡ã®ã·ããªãªãã¹ïŒ simulations/ZozoSessionApiBasicLoadTest.scala ïŒãå
¥åããŠããŸãã > sub-agentã䜿çšããŠGatlingïŒScalaïŒã·ããªãªãk6ã«ç§»è¡ããã ⺠äºè§£ããŸãããGatlingïŒScalaïŒã·ããªãªãk6ã«ç§»è¡ããããã®ãµããšãŒãžã§ã³ããèµ·åããŸãã ãŸããç§»è¡å¯Ÿè±¡ã®Gatlingã·ããªãªãç¹å®ãããããçŸåšã®ãªããžããªæ§é ã確èªãããŠãã ããã âº å€æ°ã®Gatlingã·ããªãªããããŸããã©ã®ã·ããªãªãç§»è¡ãããã確èªãããŠãã ããã â ç§»è¡å¯Ÿè±¡ ã©ã®Gatlingã·ããªãªãk6ã«ç§»è¡ããŸããïŒç¹å®ã®ã·ããªãªãæå®ããããç§»è¡å¯Ÿè±¡ã®ã«ããŽãªãæããŠãã ããã ⯠1. member-id-pf-sreé¢é£ zozo-id, zozo-session-api, zozo-member-apiçã®ã·ããªãª 2. ç¹å®ã®ãã¡ã€ã«ãæå® ãã¡ã€ã«åãŸãã¯ãã¹ã§åå¥ã«æå® 3. ãã¹ãŠã®ã·ããªãª simulations/é
äžã®å
šã·ããªãªãé æ¬¡ç§»è¡ 4. ãã®ä» ã«ã¹ã¿ã ã§æå® 5. Type something. Enter to select · Tab/Arrow keys to navigate · Esc to cancel ãµããšãŒãžã§ã³ããèµ·åããç§»è¡åŠçãå®è¡ãããŸãã ⺠simulations/ZozoSessionApiBasicLoadTest.scalaãk6ã«ç§»è¡ããŸããk6-scenario-migratorãšãŒãžã§ã³ããèµ·åããŸãã ⺠k6-scenario-migrator(Migrate ZozoSessionApi to k6) â¿ +13 more tool uses (ctrl+o to expand) · Sketching⊠(esc to interrupt · 2m 38s · â 440 tokens) 以äžãç§»è¡çµæã§ãã ⺠移è¡å®äº ð simulations/ZozoSessionApiBasicLoadTest.scala ã k6 ã«æ£åžžã«ç§»è¡ããŸããã äœæããããã¡ã€ã« | ãã¡ã€ã« | ãã¹ | |------------|-----------------------------------------------------------------------------------| | k6ã·ããªãª | k6_scenarios/scenario_member_id_sre/zozo_session_api/basic_load_test/test.js | | TestRun CR | k6_scenarios/scenario_member_id_sre/zozo_session_api/basic_load_test/testrun.yaml | ã·ããªãªæŠèŠ Webã·ããªãª (ZOZO_SESSION_PLATFORM=web): 1. Init Session à 3å 2. Save Session à 3å 3. Get Session à 57å Appã·ããªãª (ZOZO_SESSION_PLATFORM=app): 1. Init Session à 1å 2. Get Session à 100å Gatling â k6 倿ãã€ã³ã | Gatling | k6 | |-----------------------|--------------------------------| | constantUsersPerSec | constant-arrival-rate executor | | check(status.is(200)) | performRequest() + thresholds | | jsonPath().saveAs() | JSON.parse() + 倿°ä¿å | å®è¡æ¹æ³ kubectl apply -f k6_scenarios/scenario_member_id_sre/zozo_session_api/basic_load_test/testrun.yaml ã©ã®ããã«å®çŸããŠããã 以äžã®ãããªãã£ã¬ã¯ããªæ§é ã§ããµããšãŒãžã§ã³ããäœæããŠããŸãã .claude âââ agents â âââ k6-scenario-migrator.md # ãµããšãŒãžã§ã³ãã«å¯Ÿããæç€ºããã§ãã¯ãªã¹ããèšèŒ âââ docs â âââ scala-to-k6-migration-guide.md # 倿ã¬ã€ãã©ã€ã³ïŒå
·äœçãªã©ã€ãã©ãªã®äœ¿ãæ¹ã倿ã«ãŒã«ãèšèŒïŒ âââ settings.local.json åæãšããŠãããããã®ããã³ããã¯ã³ã³ãã«ãšã³ã¹äžã§æŽåãã瀟å
åãã®k6ãŠãŒã¶ãŒã¬ã€ãã©ã€ã³ãããšã«ããŠããŸãããã®ãŠãŒã¶ãŒã¬ã€ãã©ã€ã³ãã³ã³ããã¹ããšããŠããµããšãŒãžã§ã³ãçšã®ããã³ãããäœæããŸããããµããšãŒãžã§ã³ãïŒ.claude/agents/k6-scenario-migrator.mdïŒã«ã¯ãäœããã¹ããããæç€ºããŠããŸãã倿ã¬ã€ãã©ã€ã³ïŒ.claude/docs/scala-to-k6-migration-guide.mdïŒã«ã¯ãã©ã倿ãã¹ãããã®è©³çްã«ãŒã«ãèšèŒããŠããŸãã ã·ããªãªç§»è¡ã®å質ãæ
ä¿ããããããµããšãŒãžã§ã³ãã§ã¯ä»¥äžã®4ã€ã®èгç¹ã§ããã³ãããèšèšããŸããã ãŸããäºå質åã«ããæ
å ±åéã§ãããšãŒãžã§ã³ãèµ·åæã«ããŒã åã»ãã€ã¯ããµãŒãã¹åã»å¯Ÿè±¡ãã¡ã€ã«ã確èªãããããšã§ãåŸç¶ã®åŠçã«å¿
èŠãªæ
å ±ãæŒããªãååŸããŸãã #### 質å1: ããŒã åã®ç¢ºèª ãã®ã·ããªãªã管èœããããŒã åãæããŠãã ããã äŸ: member-id-pf-sre, cart-sre, search-sre #### 質å2: ãã€ã¯ããµãŒãã¹åã®ç¢ºèª 察象ãšãªããã€ã¯ããµãŒãã¹åãæããŠãã ããã äŸ: zozo-id, zozo-session-api, zozo-member-api 泚æ: ã-apiããµãã£ãã¯ã¹ã¯é€å»ããã-ãã¯ã_ãã«å€æããŸãã #### 質å3: Scalaã·ããªãªãã¡ã€ã«ãã¹ã®ç¢ºèª ç§»è¡å
ã®Scalaã·ããªãªãã¡ã€ã«ã®ãã¹ãæããŠãã ããã äŸ: simulations/ZozoIdLogin.scala 次ã«ãåœåèŠåã®æç¢ºåã§ãããã£ã¬ã¯ããªåãã·ããªãªåã®ç§»è¡ã«ãŒã«ãæç€ºããããšã§ãäžè²«æ§ã®ãããã£ã¬ã¯ããªããã¡ã€ã«ã®äœæãå®çŸããŠããŸãã #### ãã€ã¯ããµãŒãã¹åãããã£ã¬ã¯ããªåãçæ 1. ã-apiããµãã£ãã¯ã¹ãé€å» - zozo-session-api â zozo-session 2. ãã€ãã³ã-ããã¢ã³ããŒã¹ã³ã¢ã_ãã«å€æ - zozo-session â zozo_session #### Scalaãã¡ã€ã«åããã·ããªãªåãçæ 1. ãã£ã¡ã«ã±ãŒã¹ãã¹ããŒã¯ã±ãŒã¹ã«å€æïŒãã£ã¬ã¯ããªåçšïŒ - ZozoIdLogin â zozo_id_login 2. ç°å¢å€æ°: K6_SCENARIOã¯ãã€ãã³ç¹ãã«çµ±äž - zozo_id_login â zozo-id-login - çç±: Datadogã¡ããªã¯ã¹ã§äžè²«æ§ãä¿ã€ãã #### æçµçãªãã£ã¬ã¯ããªãã¹ k6_scenarios/scenario_<ããŒã å > /<ãã€ã¯ããµãŒãã¹å > / < scenario _name> / ç¶ããŠããã§ãã¯ãªã¹ãã«ããå質æ
ä¿ã§ããç§»è¡å®äºåŸã«ç¢ºèªãã¹ãé
ç®ããªã¹ãåããå
±éã©ã€ãã©ãªã®äœ¿çšãDatadogã¿ã°ã®èšå®æŒããé²ãã§ããŸãã - [ ] ` performRequest ` 颿°ã䜿çšããŠãã - [ ] ` tags ` ãã©ã¡ãŒã¿ããã¹ãŠã®HTTPãªã¯ãšã¹ãã«æž¡ããŠãã - [ ] feederã©ã€ãã©ãªïŒ ` csv ` , ` array ` ãªã©ïŒãé©åã«äœ¿çšããŠãã - [ ] ç°å¢å€æ°ã«ããã©ã«ãå€ãèšå®ããŠãã - [ ] K6 _ TEST _ RUN _ ID, K6 _ ENV, K6 _ SCENARIO, K6 _ TEAMãèšå®ããŠãã - [ ] testrun.yamlã® ` K6_SCENARIO ` ãštest.jsã® ` options.scenarios ` ã®ããŒåãåãïŒãã€ãã³ç¹ãïŒã«ãªã£ãŠãã - [ ] testrun.yamlã« ` cleanup: post ` ãèšå®ããŠããïŒãã¹ãçµäºåŸã«Podãèªååé€ããããïŒ - [ ] CSVããŒã¿ã«ç§å¿æ
å ±ãå«ãŸãããã©ããã倿ããŠãã - [ ] ç§å¿æ
å ±ãå«ãCSVã®å ŽåãExternal Secretsã§ç®¡çããŠãã - [ ] ç§å¿æ
å ±ãå«ãCSVã®å ŽåãExternalSecretãããã§ã¹ãïŒsecrets.yamlïŒãäœæããŠãã - [ ] ç§å¿æ
å ±ãå«ãCSVã®å ŽåãAWS Secrets Managerã«CSV圢åŒã®ãã¬ãŒã³ããã¹ãããŒã¿ãç»é²ããå¿
èŠãããããšããŠãŒã¶ãŒã«äŒããŠãã - [ ] ç§å¿æ
å ±ãå«ãŸãªãCSVã®å Žåã ` k6_scenarios/resources/scenario_<ããŒã å>/ ` é
äžã«é
眮ããŠãã - [ ] CSVããŒã¿ãExternal Secretsããèªã¿èŸŒãå Žåãé©åã«ããŠã³ãã»èªã¿èŸŒã¿åŠçãå®è£
ããŠãã - [ ] CSVããŒã¿ãããŒã«ã«ãã¡ã€ã«ããèªã¿èŸŒãå Žåãæ£ããçžå¯Ÿãã¹ã䜿çšããŠãã - [ ] CSVããŒã¿èªã¿èŸŒã¿æã®ãšã©ãŒãã³ããªã³ã°ãå®è£
ãããŠãã - [ ] ãã£ã¬ã¯ããªæ§æã ` scenario_<ããŒã å>/<ãã€ã¯ããµãŒãã¹å>/<scenario_name> ` ã«æºæ ããŠãã - [ ] çžå¯Ÿãã¹ã§ã®ã€ã³ããŒããæ£ããïŒ ` ../../../common_lib/... ` ïŒ - [ ] testrun.yamlãäœæãããŠããïŒ**å¿
é **ïŒ - [ ] testrun.yamlã®localFileãã¹ãæ£ãã - [ ] testrun.yamlã®TARGET _ SCENARIOãã¹ãæ£ãã - [ ] testrun.yamlã®metadata.nameãé©åã«èšå®ãããŠãã - [ ] ååšããªãã»äžèŠãªimportã¯è¡ããªã - [ ] ex ` import { open } from 'k6'; ` 次ã«ãã»ãã¥ãªãã£ãèæ
®ããåå²ã§ããCSVããŒã¿ã®æ§è³ªïŒç§å¿æ
å ±ã®æç¡ïŒã«å¿ããŠåŠçãåå²ãããæ©å¯æ
å ±ããªããžããªãžå«ããªãããã«ããŠããŸãã #### ãã¿ãŒã³1: ç§å¿æ
å ±ãå«ãCSVïŒExternal Secrets䜿çšïŒ 察象: ãã°ã€ã³æ
å ±ãAPIããŒã¯ã³ãªã©ã®ç§å¿æ
å ± â AWS Secrets Managerã«ç»é²ããExternal Secretsã§ããŠã³ã #### ãã¿ãŒã³2: ç§å¿æ
å ±ãå«ãŸãªãCSVïŒããŒã«ã«ãã¡ã€ã«ïŒ 察象: ååäžèЧãã«ããŽãªæ
å ±ãªã©ã®å
¬éããŒã¿ â k6 _ scenarios/resources/scenario_<ããŒã å > / ã«é
眮 æåŸã«ãå
¥åäŸãšåºåäŸã®èšèŒã§ããSREã«å ãéçºè
ããã®ã·ããªãªç§»è¡ã宿œããå¯èœæ§ããããããç§»è¡åŸã®åäœæ¹æ³ãªã©ãå
·äœçã«æèšããããã«åäœç¢ºèªã宿œã§ããåºåãç®æããŸããã ## äŸ ### å
¥åäŸ ``` ããŒã å: member-id-pf-sre ãã€ã¯ããµãŒãã¹å: zozo-session-api Scalaãã¡ã€ã«: simulations/ZozoSessionProxyTest.scala ``` ### åºåäŸ ``` â k6ã·ããªãªã®çæãå®äºããŸãã çæããããã¡ã€ã«: - k6_scenarios/scenario_member_id_sre/zozo_session/zozo_session_proxy_test/test.js - k6_scenarios/scenario_member_id_sre/zozo_session/zozo_session_proxy_test/testrun.yaml - k6_scenarios/scenario_member_id_sre/zozo_session/zozo_session_proxy_test/README.md ãã£ã¬ã¯ããªæ§é : k6_scenarios/scenario_member_id_sre/zozo_session/zozo_session_proxy_test/ âââ test.js âââ testrun.yaml âââ README.md å®è¡æ¹æ³ïŒKubernetesïŒ: # TestRunã®é©çš kubectl apply -f k6_scenarios/scenario_member_id_sre/zozo_session/zozo_session_proxy_test/testrun.yaml # ãã°ç¢ºèª kubectl logs -l k6_cr=zozo-session-proxy-test --tail=100 -f # TestRunã®åé€ kubectl delete testrun zozo-session-proxy-test 泚æ: ããŒã«ã«å®è¡ïŒk6ã³ãã³ãïŒã¯äœ¿çšããŸãããå¿
ãKubernetesäžã§å®è¡ããŠãã ããã ``` 以äžãããµããšãŒãžã§ã³ãã«èšèŒããŠããäž»ãªããã³ããã®å
容ã§ãã ãµããšãŒãžã§ã³ãã§äœ¿çšãã倿ã¬ã€ãã©ã€ã³ã¯ã瀟å
çšã®k6äœ¿çšæ¹æ³ããã¥ã¡ã³ããèœãšã蟌ã圢ã§äœæããŠããŸããå
容ãšããŠã¯ãå
·äœçãªã©ã€ãã©ãªã®äœ¿çšäŸãã·ããªãªãç§»è¡ããéã®å
·äœçãªBefore/Afterã®ãœãŒã¹ã³ãŒããèšèŒããŠããŸãã äžèšã«ãã¬ã€ãã©ã€ã³ãšãªãããã³ããã®å
šæãèšèŒããŸãã ããã³ããã®å
šæ # Gatling (Scala) ãã k6 (JavaScript) ãžã®ã·ããªãªç§»è¡ããã³ãã ## æŠèŠ ãã®ããã¥ã¡ã³ãã¯ãæ¢åã®GatlingïŒScalaïŒã§èšè¿°ãããè² è·è©Šéšã·ããªãªããk6ïŒJavaScriptïŒã«ç§»è¡ããããã®çæAIããã³ããã§ãã zozo-benchmarkãªããžããªã®å
±éã©ã€ãã©ãªã掻çšããå¹ççãã€æšæºåãããk6ã·ããªãªãçæããŸãã ## åææ¡ä»¶ - zozo-benchmarkãªããžããªã®æ¢åå®è£
ãåç
§ããããš - k6-operatorã®TestRun CRDã䜿çšããKubernetesç°å¢ã§ã®å®è¡ãæ³å® - å
±éã©ã€ãã©ãªïŒfeeder, metricsïŒã掻çšããããš - External Secretsã§ã·ãŒã¯ã¬ãã管çãè¡ãããš --- ## ç§»è¡ããã³ãã 以äžã®ããã³ãããçæAIã«å
¥åããŠãã ãããScalaã·ããªãªãæ·»ä»ããããšã§ãk6ã·ããªãªãžå€æãããŸãã ``` # ã¿ã¹ã¯ 以äžã®GatlingïŒScalaïŒã·ããªãªãk6ïŒJavaScriptïŒã·ããªãªã«å€æããŠãã ããã zozo-benchmarkãªããžããªã®æšæºã«åŸããå
±éã©ã€ãã©ãªãšãã¹ããã©ã¯ãã£ã¹ãé©çšããŠãã ããã --- # 倿ã«ãŒã« ## 1. åºæ¬æ§é ### ç°å¢å€æ°ã®ååŸ **Scala (Before)** ```scala val endpoint = sys.env.getOrElse("ENDPOINT", "xxxx") val clientToken = sys.env.getOrElse("CLIENT_TOKEN", "dummy123456789") val usersPerSec = sys.env.getOrElse("CONCURRENCY", "1").toInt val durationSec = sys.env.getOrElse("DURATION", "10").toInt ``` **JavaScript (After)** ```javascript // ãªãã·ã§ã³ã§äœ¿çšããç°å¢å€æ°ã«ã¯å¿
ãããã©ã«ãå€ãèšå® const ENDPOINT = __ENV.ENDPOINT || 'xxxx'; const CLIENT_TOKEN = __ENV.CLIENT_TOKEN || 'dummy123456789'; const VUS = __ENV.VUS ? parseInt(__ENV.VUS) : 1; const RATE = __ENV.RATE ? parseInt(__ENV.RATE) : 1; const DURATION = __ENV.DURATION || '10s'; ``` ## 2. HTTPãããã³ã«èšå®ãšããã㌠### HTTPãããã³ã«ãšããŒã¹URL **Scala (Before)** ```scala val httpProtocol = http .baseUrl(endpoint) .userAgentHeader("Mozilla/5.0...") .shareConnections val requestHeaders = Map( "Content-Type" -> "application/json;charset=UTF-8", "Accept-Encoding" -> "gzip, deflate", "accept" -> "application/json", "zozo-api-client-token" -> clientToken ) ``` **JavaScript (After)** ```javascript // k6ã§ã¯ããŒã¹URLããªãã·ã§ã³ãšããŠèšå®ããããªã¯ãšã¹ãããšã«ãã«URLãæå® // å
±éããããŒã¯å€æ°ãšããŠå®çŸ©ãããªã¯ãšã¹ãæã«äœ¿çš const commonHeaders = { 'Content-Type': 'application/json;charset=UTF-8', 'Accept-Encoding': 'gzip, deflate', 'Accept': 'application/json', 'zozo-api-client-token': CLIENT_TOKEN, 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0' }; ``` ## 3. ãã£ãŒããŒïŒãã¹ãããŒã¿ïŒ ### CSVããŒã¿ã®èªã¿èŸŒã¿ãã¿ãŒã³ CSVããŒã¿ã®èªã¿èŸŒã¿æ¹æ³ã¯ã**ããŒã¿ã®æ§è³ªã«ãã£ãŠ2ã€ã®ãã¿ãŒã³**ã«åãããŸãïŒ #### ãã¿ãŒã³1: ç§å¿æ
å ±ãå«ãCSVïŒExternal Secrets䜿çšïŒ **察象ããŒã¿:** - ãã°ã€ã³æ
å ±ïŒãŠãŒã¶ãŒåããã¹ã¯ãŒãïŒ - APIããŒã¯ã³ãã¢ã¯ã»ã¹ã㌠- ãã®ä»ã®èªèšŒæ
å ± **éèŠ:** ç§å¿æ
å ±ãå«ãCSVãã¡ã€ã«ã¯ã**ãªããžããªã«å«ãã**ãExternal Secretsã䜿çšããŠAWS Secrets Managerããèªã¿èŸŒã¿ãŸãã **æé :** 1. **AWS Secrets Managerã«CSVããŒã¿ãç»é²** ``` # AWS Secrets Managerã«ãã¬ãŒã³ããã¹ããšããŠCSVå
容ãä¿å # ããŒ: zozo-id/login_info # å€: CSV圢åŒã®ãã¬ãŒã³ããã¹ã user_id,password test_user_1,password123 test_user_2,password456 ``` 2. **ExternalSecretãããã§ã¹ããäœæ** ( ` secrets.yaml ` ) ```yaml apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: zozo-id-login-info-secrets-xxxx namespace: default spec: refreshInterval: "0" secretStoreRef: name: cluster-secretstore kind: ClusterSecretStore data: - secretKey: login_info_zozo_id remoteRef: key: zozo-id/login_info ``` 3. **testrun.yamlã§SecretãããŠã³ã** ```yaml spec: runner: volumes: - name: login-secrets secret: secretName: zozo-id-login-info-secrets-xxxx volumeMounts: - name: login-secrets mountPath: /secrets readOnly: true ``` 4. **test.jsã§Secretããèªã¿èŸŒã¿** ```javascript import { csv } from '../../../common_lib/feeder/feeder.js'; import { open } from 'k6'; // Load login credentials from Secret (mounted at /secrets/login_info_zozo_id) let loginFeeder; try { const secretData = open('/secrets/login_info_zozo_id'); if (!secretData || secretData.trim() === '') { throw new Error('Secret file is empty'); } loginFeeder = csv(secretData, {}, 'login-info'); console.log('[INFO] Loaded login info from Secret: /secrets/login_info_zozo_id'); } catch (error) { console.error('[ERROR] Failed to load login info from Secret:', error.message); console.error('[DEBUG] Check: kubectl get secret <secret-name> -o yaml'); throw new Error('Login info data source not available. Secret is required.'); } // default颿°å
ã§äœ¿çš export default function () { const userData = loginFeeder.circular(); // userData.user_id, userData.password ãªã©ãå©çšå¯èœ } ``` **åç
§å®è£
:** - ExternalSecretãããã§ã¹ã: ` k6_scenarios/scenario_member_id_sre/zozo_id/secrets.yaml ` - test.jså®è£
: ` k6_scenarios/scenario_member_id_sre/zozo_id/login_scenario/test.js ` (44-55è¡ç®) --- #### ãã¿ãŒã³2: ç§å¿æ
å ±ãå«ãŸãªãCSVïŒããŒã«ã«ãã¡ã€ã«ïŒ **察象ããŒã¿:** - ååäžèЧ - ã«ããŽãªæ
å ± - ãã¹ãçšã®å
¬éããŒã¿ **é
çœ®å Žæ:** ``` k6_scenarios/resources/scenario_<team_name>/ âââ *.csv # äŸ: products.csv, categories.csv ãªã© # ããŒã å¥ã®äŸ k6_scenarios/resources/scenario_member_id_sre/ âââ products.csv âââ categories.csv k6_scenarios/resources/scenario_cart_sre/ âââ cart_items.csv âââ user_sessions.csv ``` **泚æ:** - åºæ¬ã¯ ` scenario_<team_name>/ ` çŽäžã«é
眮 - æŽçã®ãããããã«ãµããã£ã¬ã¯ããªãäœãããšãå¯èœïŒäŸ: ` scenario_samples/feeder/ ` ïŒ - ãµããã£ã¬ã¯ããªãäœãå Žåã¯ãimportãã¹ãé©åã«èª¿æŽããŠãã ãã **Scala (Before)** ```scala val feeder = csv("products.csv") // ã·ããªãªå
ã§äœ¿çš .feed(feeder.circular) ``` **JavaScript (After)** ```javascript import { csv } from '../../../common_lib/feeder/feeder.js'; import { open } from 'k6'; // CSVãã¡ã€ã«ãèªã¿èŸŒã¿ïŒresourcesãã£ã¬ã¯ããªããçžå¯Ÿãã¹ã§åç
§ïŒ // ãã¹ã®äŸ: scenario_member_id_sre ã®ããŒã ã®å Žå const csvData = open('../../../resources/scenario_member_id_sre/products.csv'); const productsFeeder = csv(csvData, {}, 'products'); // default颿°å
ã§äœ¿çš export default function () { // circular(): é çªã«1ã€ãã€éžæïŒã©ãŠã³ãããã³ïŒ const product = productsFeeder.circular(); // random(): ã©ã³ãã ã«éžæ // const product = productsFeeder.random(); // perVu(): VUããšã«åºå®ããŒã¿ãå²ãåœãŠ // const product = productsFeeder.perVu(); // productã«ã¯ CSV ã®åã«ã©ã ãããããã£ãšããŠå«ãŸãã // äŸ: product.product_id, product.name, product.price ãªã© } ``` ### Feederã®ã¡ãœãã - ` circular() ` : ã©ãŠã³ãããã³ã§é çªã«éžæïŒGatlingã®circularãšåãïŒ - ` random() ` : ã©ã³ãã ã«éžæïŒGatlingã®randomãšåãïŒ - ` perVu() ` : VUããšã«åºå®ããŒã¿ãå²ãåœãŠïŒGatlingã®queueãšåãïŒ ### AtomicIntegerã®å€æïŒãŠããŒã¯IDçæïŒ **éèŠ**: k6ã«ã¯Gatlingã® ` AtomicInteger ` ã®ãããªã¹ã¬ããã»ãŒããªã«ãŠã³ã¿ãŒããããŸããã è€æ°ã®VUãåæã«å®è¡ãããç°å¢ã§ãŠããŒã¯ãªIDãçæããã«ã¯ãVU IDãšã€ãã¬ãŒã·ã§ã³çªå·ãçµã¿åãããå¿
èŠããããŸãã **Scala (Before)** ```scala // åæå€ã¯ç°å¢å€æ°ã®CURRENT_MAX_MEMBER_ID val memberIdCounter = new AtomicInteger(currentMaxMemberId) // ã«ã¹ã¿ã feederãäœæããmember_idãã€ã³ã¯ãªã¡ã³ãããŠãã val memberIdFeeder = Iterator.continually(Map("member_id" -> memberIdCounter.incrementAndGet())) var single_request = scenario("test scenario") .feed(memberIdFeeder) .exec(...) ``` **JavaScript (After)** ```javascript import exec from 'k6/execution'; const CURRENT_MAX_MEMBER_ID = __ENV.CURRENT_MAX_MEMBER_ID ? parseInt(__ENV.CURRENT_MAX_MEMBER_ID) : 0; export default function () { // åVUã«ç¬ç«ããID空éãå²ãåœãŠãŠãŠããŒã¯ãªIDãçæ // VU 1: 1000001, 1000002, 1000003, ... // VU 2: 2000001, 2000002, 2000003, ... // ããã«ãããè€æ°ã®VUãåæã«å®è¡ããŠãéè€ãçºçããªã const memberId = CURRENT_MAX_MEMBER_ID + (exec.vu.idInTest * 1000000) + exec.scenario.iterationInInstance + 1; // memberIdã䜿çšãããªã¯ãšã¹ã performRequest('example', (tags) => { return http.post(`${ENDPOINT}/api/endpoint`, JSON.stringify({ member_id: memberId }), { headers: { ...commonHeaders }, tags } ); }, 200); } ``` **説æ**: - ` exec.vu.idInTest ` : VUããšã«äžæã®IDïŒ1, 2, 3, ...ïŒ - ` exec.scenario.iterationInInstance ` : ãã®VUå
ã§ã®ã€ãã¬ãŒã·ã§ã³çªå·ïŒ0, 1, 2, ...ïŒ - ` 1000000 ` : åVUã«100äžã®IDã¹ããŒã¹ã確ä¿ïŒVUããšã«100äžå以äžã€ãã¬ãŒã·ã§ã³ããå Žåã¯èª¿æŽãå¿
èŠïŒ - ãã®æ¹æ³ã«ããã409 Conflictãªã©ã®IDéè€ãšã©ãŒã鲿¢ã§ããŸã **çæäŸ**: - ` CURRENT_MAX_MEMBER_ID=0 ` ã®å Žå: - VU 1, iteration 0: 1000001 - VU 1, iteration 1: 1000002 - VU 2, iteration 0: 2000001 - VU 2, iteration 1: 2000002 ## 4. HTTPãªã¯ãšã¹ã ### ãªã¯ãšã¹ãã®å€æïŒperformRequest颿°ã䜿çšïŒ **éèŠ**: å¿
ã ` performRequest ` 颿°ã䜿çšããŠãã ãããããã«ããïŒ - Datadogãžã®ã¡ããªã¯ã¹éä¿¡ - ã«ã¹ã¿ã ã¿ã°ã®èªåä»äž - æšæºåããããšã©ãŒãã³ããªã³ã° ãå®çŸãããŸãã **Scala (Before)** ```scala val login = exec( http("Login") .post("/auth/v1/login") .headers(requestHeaders) .body(StringBody("""{"id": "${email}","password": "${password}"}""")) .check(status.is(200)) .check(jsonPath("$.id_token").saveAs("id_token")) .check(jsonPath("$.refresh_token").saveAs("refresh_token")) ) ``` **JavaScript (After)** ```javascript import { performRequest } from '../common_lib/metrics/metrics.js'; import http from 'k6/http'; export default function () { const userData = userFeeder.circular(); // performRequest(ãªã¯ãšã¹ãå, ãªã¯ãšã¹ã颿°, æåŸ
ããã¹ããŒã¿ã¹ã³ãŒã) const loginResponse = performRequest('login', (tags) => { const payload = JSON.stringify({ id: userData.email, password: userData.password }); return http.post(`${ENDPOINT}/auth/v1/login`, payload, { headers: { ...commonHeaders, }, tags // å¿
ãtagsãæž¡ãïŒDatadogã®ã«ã¹ã¿ã ã¿ã°ä»äžã®ããïŒ }); }, 200); // ã¬ã¹ãã³ã¹ããããŒã¿ãåãåºã const loginData = JSON.parse(loginResponse.body); const idToken = loginData.id_token; const refreshToken = loginData.refresh_token; } ``` ### åçš®HTTPã¡ãœããã®äŸ **GET ãªã¯ãšã¹ã** ```javascript performRequest('healthcheck', (tags) => { return http.get(`${ENDPOINT}/auth/v1/healthcheck/db`, { headers: commonHeaders, tags }); }, 200); ``` **POST ãªã¯ãšã¹ãïŒJSONããã£ïŒ** ```javascript performRequest('create_user', (tags) => { const payload = JSON.stringify({ email: userData.email, zozo_id: userData.zozo_id, password: userData.password_hash }); return http.post(`${ENDPOINT}/auth/v1/members/${userData.user_id}`, payload, { headers: commonHeaders, tags }); }, 200); ``` **PUT ãªã¯ãšã¹ã** ```javascript performRequest('update_user', (tags) => { const payload = JSON.stringify({ email: userData.email, zozo_id: userData.zozo_id, password: userData.password_hash }); return http.put(`${ENDPOINT}/auth/v1/members/${userData.user_id}`, payload, { headers: commonHeaders, tags }); }, 200); ``` **DELETE ãªã¯ãšã¹ã** ```javascript performRequest('delete_user', (tags) => { return http.del(`${ENDPOINT}/auth/v1/members/${userData.user_id}`, null, { headers: commonHeaders, tags }); }, 200); ``` **èªèšŒããããŒä»ããªã¯ãšã¹ã** ```javascript performRequest('logout', (tags) => { return http.post(`${ENDPOINT}/auth/v1/logout`, null, { headers: { ...commonHeaders, 'Authorization': `Bearer ${idToken}` }, tags }); }, 200); ``` ## 5. ã·ããªãªãšãªãã·ã§ã³èšå® ### ã·ããªãªèšå®ã®å€æ **éèŠ**: testrun.yamlã® ` K6_SCENARIO ` ç°å¢å€æ°ãštest.jsã® ` options.scenarios ` ã®ããŒåã¯**å¿
ãåãååïŒãã€ãã³ç¹ãïŒ**ã«ããŠãã ããã çç±ïŒ - ` http_req ` ã¡ããªã¯ã¹: performRequest颿°ã§ ` K6_SCENARIO ` ã®å€ãã¿ã°ãšããŠä»äžããã - ` iterations ` ã¡ããªã¯ã¹: k6ã®ãšã°ãŒãã¥ãŒã¿ã ` options.scenarios ` ã®ããŒåãã¿ã°ãšããŠèªåä»äžãã - ååãç°ãªããšãDatadogããã·ã¥ããŒãã§2ã€ã®ç°ãªãscenarioåã衚瀺ãããã¡ããªã¯ã¹ãåæããã **Scala (Before)** ```scala val single_request = scenario("zozo-id login") .feed(feeder.circular) .exec(captcha) .exec(login) .exec(refresh_token) .exec(validate_token) .exec(logout) setUp( single_request.inject( constantUsersPerSec(usersPerSec) during(durationSec seconds) ).protocols(httpProtocol) ) ``` **JavaScript (After)** ```javascript import { sleep } from 'k6'; // ãªãã·ã§ã³èšå® // 泚æ: scenariosã®ããŒå㯠testrun.yamlã®K6_SCENARIOãšåããã€ãã³ç¹ãã«ãã export const options = { scenarios: { 'zozo-id-login': { // testrun.yamlã®K6_SCENARIO="zozo-id-login"ãšäžèŽããã // äžå®ã®ã¬ãŒãã§ãªã¯ãšã¹ããéä¿¡ executor: 'constant-arrival-rate', // 1ç§ãããã®ã€ãã¬ãŒã·ã§ã³æ° rate: RATE, // ã¬ãŒãã®é©çšæé timeUnit: '1s', // äºåã«ç¢ºä¿ããVUæ° preAllocatedVUs: VUS, // ãã¹ãæé duration: DURATION, // æå€§VUæ°ïŒå¿
ãpreAllocatedVUs以äžïŒ maxVUs: 100, }, }, }; // ã»ããã¢ããåŠçïŒãã¹ãéå§æã«1åã ãå®è¡ïŒ export function setup() { console.log('Execution parameters:', JSON.stringify({ ENDPOINT, VUS, RATE, DURATION, }, null, 2)); } // ã¡ã€ã³ã®ãã¹ãåŠç export default function () { const userData = userFeeder.circular(); // ãªã¯ãšã¹ããé çªã«å®è¡ performRequest('captcha', (tags) => { return http.get(`${ENDPOINT}/auth/v1/irregular-login/detect`, { headers: commonHeaders, tags }); }, 200); const loginResponse = performRequest('login', (tags) => { const payload = JSON.stringify({ id: userData.email, password: userData.password }); return http.post(`${ENDPOINT}/auth/v1/login`, payload, { headers: commonHeaders, tags }); }, 200); const loginData = JSON.parse(loginResponse.body); const idToken = loginData.id_token; const refreshToken = loginData.refresh_token; // 以éã®ãªã¯ãšã¹ããç¶ãã... // å¿
èŠã«å¿ããŠsleepã远å ïŒThink TimeïŒ // sleep(1); } // ã¯ãªãŒã³ã¢ããåŠçïŒãã¹ãçµäºæã«1åã ãå®è¡ïŒ export function teardown(data) { console.log('Test completed'); } ``` ### Executorã®éžæ Gatlingã® ` constantUsersPerSec ` ã¯ãk6ã® ` constant-arrival-rate ` ã«å¯Ÿå¿ããŸãã **ãã®ä»ã®Executorãªãã·ã§ã³** 1. **constant-vus**: åºå®æ°ã®VUãäžå®æéå®è¡ ```javascript executor: 'constant-vus', vus: 10, duration: '30s', ``` 2. **ramping-vus**: VUæ°ã段éçã«å¢æž ```javascript executor: 'ramping-vus', startVUs: 0, stages: [ { duration: '30s', target: 10 }, { duration: '1m', target: 10 }, { duration: '30s', target: 0 }, ], ``` 3. **ramping-arrival-rate**: å°çã¬ãŒããæ®µéçã«å¢æž ```javascript executor: 'ramping-arrival-rate', startRate: 5, timeUnit: '1s', preAllocatedVUs: 10, maxVUs: 100, stages: [ { duration: '30s', target: 50 }, { duration: '1m', target: 50 }, { duration: '30s', target: 0 }, ], ``` ## 6. Datadogã¡ããªã¯ã¹ãšã«ã¹ã¿ã ã¿ã° ### ç°å¢å€æ°ã§ã«ã¹ã¿ã ã¿ã°ãèšå® TestRun YAMLãã¡ã€ã«ïŒ ` testrun.yaml ` ïŒã§ä»¥äžã®ç°å¢å€æ°ãèšå®ããŠãã ããïŒ ```yaml spec: runner: env: # ãã¹ãå®è¡IDïŒå¿
é ïŒDatadogããã·ã¥ããŒãã§ã®ãã£ã«ã¿ãªã³ã°ã«äœ¿çšïŒ - name: K6_TEST_RUN_ID value: "zozo-id-login-stg-2025-10-22" # ç°å¢åïŒå¿
é ïŒ - name: K6_ENV value: "stg" # ã·ããªãªåïŒå¿
é ïŒtest.jsã®options.scenariosã®ããŒåãšäžèŽãããïŒ - name: K6_SCENARIO value: "zozo-id-login" # ããŒã åïŒå¿
é ïŒ - name: K6_TEAM value: "member-id-pf-sre" # ãã®ä»ã®ç°å¢å€æ° - name: ENDPOINT value: "xxxx" - name: VUS value: "10" - name: RATE value: "5" - name: DURATION value: "60s" ``` ### ã«ã¹ã¿ã ã¡ããªã¯ã¹ã®çæïŒãªãã·ã§ã³ïŒ åºæ¬çãªã¡ããªã¯ã¹ã¯èªåçã«Datadogã«éä¿¡ãããŸãããã«ã¹ã¿ã ã¡ããªã¯ã¹ã远å ããå Žåã¯ä»¥äžã®ããã«ããŸãïŒ **泚æ**: ã«ã¹ã¿ã ã¡ããªã¯ã¹ãçæããå Žåã³ã¹ããçºçããããã@platform-sreããŒã ã«çžè«ããŠãã ããã ```javascript import { Counter, Trend, Rate, Gauge } from 'k6/metrics'; // ã«ã¹ã¿ã ã¡ããªã¯ã¹ã®å®çŸ© const errorCounter = new Counter('custom_errors'); const loginDuration = new Trend('custom_login_duration'); const successRate = new Rate('custom_success_rate'); export default function () { const startTime = Date.now(); const loginResponse = performRequest('login', (tags) => { // ... ãªã¯ãšã¹ãåŠç }, 200); if (loginResponse.status === 200) { successRate.add(1); loginDuration.add(Date.now() - startTime); } else { successRate.add(0); errorCounter.add(1); } } ``` ## 7. External Secretsã®äœ¿çš ### ã·ãŒã¯ã¬ããã®åç
§ TestRun YAMLãã¡ã€ã«ã§External Secretsãåç
§ããŸãïŒ ```yaml apiVersion: k6.io/v1alpha1 kind: TestRun metadata: name: zozo-id-login-test spec: parallelism: 4 script: localFile: /test/zozo-benchmark/k6_scenarios/zozo-id-login/test.js runner: image: grafana/k6:0.54.0 env: # External Secretsããã·ãŒã¯ã¬ãããååŸ - name: CLIENT_TOKEN valueFrom: secretKeyRef: name: zozo-id-client-token-secret key: token # ConfigMapããèšå®ãååŸ - name: K6_TEST_RUN_ID value: "zozo-id-login-stg-2025-10-22" envFrom: # GitHubèªèšŒçšã·ãŒã¯ã¬ããïŒå¿
é ïŒ - secretRef: name: k6-github-app-secrets-xxxx # Datadogèšå®çšConfigMapïŒå¿
é ïŒ - configMapRef: name: k6-system-config ``` ### Secretsã®ç®¡çå Žæ ã·ãŒã¯ã¬ããæ
å ±ã¯ä»¥äžã§ç®¡çãããŸãïŒ - **k6-github-app-secrets**: GitHubèªèšŒçšïŒã·ããªãªã¯ããŒã³çšïŒ - ` k8s/main/k6-secrets/secrets.yaml ` - **gatling-secrets**: æ¢åã®Gatlingã·ãŒã¯ã¬ããïŒAPI tokenãªã©ïŒ - ` k8s/main/gatling-secrets/secrets.yaml ` ## 8. ãã£ã¬ã¯ããªæ§æ ### ã·ããªãªãã¡ã€ã«ã®é
眮 ``` zozo-benchmark/ âââ k6_scenarios/ âââ common_lib/ # å
±éã©ã€ãã©ãª â âââ feeder/ â â âââ feeder.js â âââ metrics/ â âââ metrics.js â âââ tags.js âââ resources/ # ãã¹ãããŒã¿ïŒããŒã å¥ã«ç®¡çïŒ â âââ scenario_<ããŒã å>/ # ããŒã å¥ãªãœãŒã¹ãã£ã¬ã¯ã㪠â âââ *.csv # ç§å¿æ
å ±ãå«ãŸãªãCSVãã¡ã€ã« âââ scenario_<ããŒã å>/ # ããŒã å¥ã·ããªãªãã£ã¬ã¯ã㪠âââ <ãã€ã¯ããµãŒãã¹å>/ # ãã€ã¯ããµãŒãã¹å¥ãã£ã¬ã¯ã㪠âââ <scenario_name>/ # ã·ããªãªåãã£ã¬ã¯ã㪠âââ testrun.yaml # TestRun ãããã§ã¹ã âââ test.js # ã·ããªãªãã¡ã€ã« âââ README.md # ããã¥ã¡ã³ãïŒãªãã·ã§ã³ïŒ ``` **äŸ**: member-id-pf-sreããŒã ã®zozo-oidc-provider-apiã®AuthorizationCodeFlowãã¹ãã®å Žå ``` zozo-benchmark/ âââ k6_scenarios/ âââ resources/ â âââ scenario_member_id_sre/ â âââ test_data.csv âââ scenario_member_id_sre/ âââ zozo_oidc_provider/ âââ zozo_oidc_provider_api_authorization_code_flow_test/ âââ testrun.yaml âââ test.js âââ README.md ``` **åœåèŠå**: - **ãã£ã¬ã¯ããªå**: Scalaãã¡ã€ã«åãã¹ããŒã¯ã±ãŒã¹ïŒã¢ã³ããŒããŒç¹ãïŒã«å€æ - äŸ: ` ZozoOidcProviderApiAuthorizationCodeFlowTest.scala ` â ` zozo_oidc_provider_api_authorization_code_flow_test/ ` - **K6_SCENARIOãšoptions.scenariosã®ããŒå**: ãã€ãã³ç¹ãã«çµ±äž - äŸ: ` zozo-oidc-provider-api-authorization-code-flow-test ` - çç±: Datadogã¡ããªã¯ã¹ã§äžè²«æ§ãä¿ã€ãã ### çžå¯Ÿãã¹ã§ã®ã€ã³ããŒã ```javascript // feederã©ã€ãã©ãªã®ã€ã³ããŒã import { csv, array } from '../../../common_lib/feeder/feeder.js'; // metricsã©ã€ãã©ãªã®ã€ã³ããŒã import { performRequest } from '../../../common_lib/metrics/metrics.js'; // CSVãã¡ã€ã«ã®èªã¿èŸŒã¿ïŒresourcesãã£ã¬ã¯ããªããïŒ // ãã¹ã®äŸ: scenario_member_id_sre ã®ããŒã ã®å Žå const csvData = open('../../../resources/scenario_member_id_sre/data.csv', 'r'); ``` **äŸ**: member-id-pf-sreããŒã ã®zozo-oidc-providerã®AuthorizationCodeFlowãã¹ãã®å Žå ```javascript import { csv, array } from '../../../common_lib/feeder/feeder.js'; import { performRequest } from '../../../common_lib/metrics/metrics.js'; const csvData = open('../../../resources/scenario_member_id_sre/data.csv', 'r'); ``` --- # å€æå¯Ÿè±¡ã®Scalaã·ããªãª 以äžã®Scalaã·ããªãªãk6ã«å€æããŠãã ããïŒ [ããã«Scalaã·ããªãªã®ã³ãŒãã貌ãä»ã] --- # æåŸ
ããåºå 以äžã®2ã€ã®ãã¡ã€ã«ãçæããŠãã ããïŒ 1. **test.js**: k6ã·ããªãªãã¡ã€ã« - äžèšã®ã«ãŒã«ã«åŸã£ãŠå€æ - ãã¹ãŠã®ã€ã³ããŒããå«ãã - ã³ã¡ã³ãã§åã»ã¯ã·ã§ã³ã説æ 2. **testrun.yaml**: TestRun ãããã§ã¹ããã¡ã€ã« - é©åãªãªãœãŒã¹èšå® - ç°å¢å€æ°ãšã·ãŒã¯ã¬ããã®èšå® - initContainersã®èšå®ïŒã·ããªãªã¯ããŒã³çšïŒ 3. **README.md** (ãªãã·ã§ã³): ã·ããªãªã®èª¬æ - ãã¹ãç®ç - å®è¡æ¹æ³ - å¿
èŠãªç°å¢å€æ° ``` --- ## 䜿çšäŸ ### 1. Scalaã·ããªãªãæºå ```scala // simulations/ZozoIdLogin.scala class ZozoIdLogin extends Simulation { val endpoint = sys.env.getOrElse("ENDPOINT", "xxxx") // ... (æ¢åã®Scalaã³ãŒã) } ``` ### 2. çæAIã«å
¥å äžèšã®ãç§»è¡ããã³ãããå
šäœãã³ããŒããæåŸã®ãå€æå¯Ÿè±¡ã®Scalaã·ããªãªãã»ã¯ã·ã§ã³ã«Scalaã³ãŒãã貌ãä»ããŠãçæAIã«å
¥åããŸãã ### 3. çæãããk6ã·ããªãªãç¢ºèª çæAIã以äžãåºåããŸãïŒ - ` test.js ` : k6ã·ããªãªãã¡ã€ã« - ` testrun.yaml ` : TestRun ãããã§ã¹ã - ` README.md ` : 説æããã¥ã¡ã³ãïŒãªãã·ã§ã³ïŒ ### 4. ãªããžããªã«é
眮 ```bash # ã·ããªãªãã£ã¬ã¯ããªãäœæ # äŸ: member-id-pf-sreããŒã ã®zozo-idã®Loginãã¹ãã®å Žå mkdir -p k6_scenarios/scenario_member_id_sre/zozo_id/zozo_id_login # ãã¡ã€ã«ãé
眮 mv test.js k6_scenarios/scenario_member_id_sre/zozo_id/zozo_id_login/ mv testrun.yaml k6_scenarios/scenario_member_id_sre/zozo_id/zozo_id_login/ mv README.md k6_scenarios/scenario_member_id_sre/zozo_id/zozo_id_login/ # CSVãã¡ã€ã«ãããå ŽåïŒç§å¿æ
å ±ãå«ãŸãªããã®ã®ã¿ïŒ mv *.csv k6_scenarios/resources/ ``` ### 5. ãã¹ãå®è¡ ```bash # TestRunãé©çš # äŸ: member-id-pf-sreããŒã ã®zozo-idã®Loginãã¹ãã®å Žå kubectl apply -f k6_scenarios/scenario_member_id_sre/zozo_id/zozo_id_login/testrun.yaml # ãã°ç¢ºèª kubectl get testruns kubectl logs <pod-name> # Datadogããã·ã¥ããŒãã§çµæç¢ºèª [Datadogããã·ã¥ããŒãã®ãªã³ã¯] ``` --- ## åèè³æ - [ k6 å
¬åŒããã¥ã¡ã³ã ]( https://grafana.com/docs/k6/latest/ ) - [ k6-operator GitHub ]( https://github.com/grafana/k6-operator ) --- ## ãã©ãã«ã·ã¥ãŒãã£ã³ã° ### ã¿ã°ãä»äžãããªãå Žå **åé¡**: Datadogããã·ã¥ããŒãã§ã«ã¹ã¿ã ã¿ã°ã衚瀺ãããªã **解決ç**: 1. ` performRequest ` 颿°ã䜿çšããŠãããç¢ºèª 2. ` tags ` ãã©ã¡ãŒã¿ãHTTPãªã¯ãšã¹ãã«æž¡ããŠãããç¢ºèª 3. TestRun YAMLã§ç°å¢å€æ°ïŒK6 _ TEST _ RUN _ ID, K6 _ ENV, K6 _ SCENARIO, K6 _ TEAMïŒãèšå®ããŠãããç¢ºèª 4. ConfigMapïŒk6-system-configïŒãåç
§ããŠãããç¢ºèª ### ã·ããªãªãã¯ããŒã³ãããªãå Žå **åé¡**: TestRunå®è¡æã«ã·ããªãªãã¡ã€ã«ãèŠã€ãããªã **解決ç**: 1. initContainersãæ£ããèšå®ãããŠãããç¢ºèª 2. GitHubèªèšŒçšSecretïŒk6-github-app-secretsïŒãååšãããç¢ºèª 3. sparse-checkoutãã¿ãŒã³ãæ£ãããç¢ºèª 4. ãã©ã³ãåïŒGITSYNC _ REFïŒãæ£ãããç¢ºèª ### ã¡ããªã¯ã¹ãDatadogã«éä¿¡ãããªãå Žå **åé¡**: Datadogã«ã¡ããªã¯ã¹ã衚瀺ãããªã **解決ç**: 1. ConfigMapïŒk6-system-configïŒãåç
§ããŠãããç¢ºèª 2. OTELé¢é£ã®ç°å¢å€æ°ãèšå®ãããŠãããç¢ºèª 3. ãããã¯ãŒã¯æ¥ç¶ãç¢ºèª ç§»è¡çµæãšå¹æ å®éã«Gatlingã·ããªãªãk6ã«ç§»è¡ããçµæãèšèŒããŸãããµããšãŒãžã§ã³ããçšããŠç§»è¡ããã·ããªãªãå®è¡ãããšãããåé¡ãªããã€ã¯ããµãŒãã¹ã«æ³å®ãªã¯ãšã¹ããéä¿¡ãããŠããããšã確èªã§ããŸãããGatlingã§å®çŸããŠãã以äžã®æ©èœãk6ã§ãåæ§ã«åäœããŠããŸãã ç°å¢å€æ°ã«ããèšå®åãæ¿ãïŒãšã³ããã€ã³ããVUæ°ãå®è¡æéãªã©ïŒ CSVããã®ãã¹ãããŒã¿èªã¿èŸŒã¿ Datadogãžã®ã¡ããªã¯ã¹éä¿¡ãšã«ã¹ã¿ã ã¿ã°ä»äž å
·äœçãªå®è£
äŸãèšèŒããã ãã§ãªãããã§ãã¯ãªã¹ãã䜿çšããããšã§ã·ããªãªç§»è¡ã®ç²ŸåºŠãäžæãããšèããããŸãã äžæ¹ã§ã課é¡ãçºçããŸãããç§»è¡åŸã®ã·ããªãªã«ãŠãäžèŠãªã¢ãžã¥ãŒã«importæãçæããå®è¡ãšã©ãŒã«ãªã£ãŠããŸãããšããããŸããã // çæãããäžèŠãªimport import { open } from 'k6' ; ç§»è¡ã¬ã€ãã©ã€ã³ã«ã䜿çšçŠæ¢ã®importãã¿ãŒã³ããæç€ºçã«è¿œèšããããšã§è§£æ±ºããŸããã - [ ] äžèŠãªã»ååšããªãimportã¯è¡ããªã - [ ] ex ` import { open } from 'k6'; ` ãµããšãŒãžã§ã³ãã䜿ãäžã§ã®ãã€ã³ã ä»åã®åãçµã¿ãéããŠãã·ããªãªç§»è¡ã®ç²ŸåºŠãé«ããµããšãŒãžã§ã³ããå®è£
ã§ããŸããããã€ã³ãã¯ä»¥äžã®ãšããã§ãã ãŸããã¬ã€ãã©ã€ã³ã®æŽåãæéèŠã§ãããµããšãŒãžã§ã³ãã«æž¡ãã³ã³ããã¹ãã®è³ªãåºåå質ãçŽæ¥å·Šå³ããŸããå
·äœäŸãå€ãå«ããããšã§å€æç²ŸåºŠãåäžããŸããããã®ãããã¬ã€ãã©ã€ã³ã®å®æåºŠãã©ã®çšåºŠé«ãã§ãããããã€ã³ããšãªããŸãã æ¬¡ã«ããã§ãã¯ãªã¹ãã§å質ãæ
ä¿ããããšã§ããLLMã®åºåã«ã¯ã°ãã€ããããããããã§ãã¯ãªã¹ãã§åºåã®ç²ŸåºŠãæ
ä¿ã§ãããšèããŠããŸããç¹ã«ããã£ãŠã¯ãããªãããšããæç€ºãããšå¹æçã«å¶éã§ããŸããã æåŸã«ãå®å
šèªååã§ã¯ãªãåèªååãšããŠæããããšã§ããåçŽãªã·ããªãªã§ããã°ããµããšãŒãžã§ã³ãã®åºåãã»ãŒãã®ãŸãŸäœ¿çšå¯èœãªããšãããããŸãããäžæ¹ã§ãæçµçãªã¬ãã¥ãŒãå®éã«ã·ããªãªãå®è¡ããŠã¿ããªã©ã®åäœç¢ºèªã¯ãŸã ãŸã å¿
èŠã«æããŸããã ãµããšãŒãžã§ã³ãã«ããã·ããªãªç§»è¡ã¯ãæåã§ã®æžãæããšæ¯èŒããŠå€§å¹
ãªå·¥æ°åæžãæåŸ
ã§ããŸããåæ§ã®ç§»è¡ãæ€èšããŠããæ¹ã¯ããŸãåçŽãªã·ããªãªãã詊ããã¬ã€ãã©ã€ã³ãåŸã
ã«æ¹åããŠããã¢ãããŒããããããã§ãã ãããã« æ¬èšäºã§ã¯ãgatling-operatorã®ã¡ã³ããã³ã¹èª²é¡ãšk6å°å
¥æã®å·¥å€«ãã·ããªãªç§»è¡ã«ãããçæAIã®æŽ»çšäºäŸã«ã€ããŠç޹ä»ããŸããã ä»åã®è² è·è©ŠéšããŒã«ç§»è¡ã§ã¯ä»¥äžãå®çŸããŸããã è² è·è©ŠéšããŒã«ã®ã¡ã³ããã³ã¹è² è·è»œæž ã³ã³ããã€ã¡ãŒãžãã«ãããã®è±åŽã«ããã·ããªãªä¿®æ£ãµã€ã¯ã«ã®æ¹å Datadogããã·ã¥ããŒãã«ãã詊éšçµæã®å¯èŠåãšåæåŒ·å çæAIãæŽ»çšããã·ããªãªç§»è¡ã®å¹çå ä»åŸã¯ãæ®ãã®Gatlingã·ããªãªã®k6ãžã®ç§»è¡ãé²ãã€ã€ãClaudeãµããšãŒãžã§ã³ããžã®ããã³ãããããã«æ¹åããç§»è¡ç²ŸåºŠã®åäžã«åãçµãã§ãããããšèããŠããŸãã ZOZOã§ã¯ãäžç·ã«ãµãŒãã¹ãäœãäžããŠãããæ¹ãåéäžã§ãããèå³ã®ããæ¹ã¯ã以äžã®ãªã³ã¯ãããã²ãå¿åãã ããã hrmos.co åŒç€Ÿã®ã¡ããªã¯ã¹ä¿åæéã¯1幎éã§ããæ°žç¶çã«æ®ãããå Žåã¯ããã·ã¥ããŒãã®ã¹ã¯ãªãŒã³ã·ã§ããã§å¯Ÿå¿ããŸãã ↩