From f9811e8adca61beae7ce4dd1ff1543d3411445d0 Mon Sep 17 00:00:00 2001 From: Tommaso Borgato Date: Sat, 21 Sep 2024 16:38:20 +0200 Subject: [PATCH] WildFly mini series: REST Client --- .../simple-microservice-client-part3-1.png | Bin 0 -> 26604 bytes ...t-started-microservices-on-kubernetes.adoc | 24 +- .../_includes/_constants.adoc | 19 +- .../_includes/_titles.adoc | 8 + .../simple-microservice-client-part1.adoc | 290 +++++++ .../simple-microservice-client-part2.adoc | 293 +++++++ .../simple-microservice-client-part3.adoc | 734 ++++++++++++++++++ 7 files changed, 1359 insertions(+), 9 deletions(-) create mode 100644 assets/img/news/get-started-microservices-on-kubernetes/simple-microservice-client-part3-1.png create mode 100644 guides/get-started-microservices-on-kubernetes/simple-microservice-client-part1.adoc create mode 100644 guides/get-started-microservices-on-kubernetes/simple-microservice-client-part2.adoc create mode 100644 guides/get-started-microservices-on-kubernetes/simple-microservice-client-part3.adoc diff --git a/assets/img/news/get-started-microservices-on-kubernetes/simple-microservice-client-part3-1.png b/assets/img/news/get-started-microservices-on-kubernetes/simple-microservice-client-part3-1.png new file mode 100644 index 0000000000000000000000000000000000000000..ea84c130e14c174e0640a21f38e1dfe6c52ed3f4 GIT binary patch literal 26604 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

oK(ot?r??9!gab^kw`YEMqTS)@y9-nkz8qWZ$lQ zFe=1NolG2mm&-DfOqI^5q~6BOR3%qpb*dfqrkMCy48MwnS{0L%?h-g9-u?>Aq#_jOwwg~(_xJF|U^U$QrxHo$oRuRdhF0F!IVv7RC{jGht#j@2z zWAnoL!Z#w0w9YS`!iRw%DBJsWnabpI{g%>AE4J3%|^`Y zG+8COy!P6Z#>|z*`B(XmB(U0;)RN!1JaanOmc951(8|nfCbT zRaLB){H{}N;eAh$N{`9(^LU@BCeKNCIn!27Pfp(X)nA_lZG1%$ z1`}}Jn6J)IP9K|_W0>Z3=b5i$g!zZFKM%;K#EVu)6kv=1g=&=FDpoI+*Bl6Y z!>9t~9ecDU{h7-uOmJM6K@6|Muq|FEUYAX!LnE5i?Ly|`58kpd?9B>T!2Nl5>D_ln zJpOv4S^3%8`>vJpdTt3t!rN*Zv|{ufK1t>G4lbyc8|B3>-`C(_W1fipZW@@R;kkeA z@LVZ_?#BE8@?zsFa{fuqQ13h%UBwU4R}tURAYS(}%iT8=BimXy$M9(DkkZsQHLCsszM#{_*8Ea;6&qN#w=L-n z?Po7QO4SXqmrOQZP4g%Bwh%4Vi9r@E4C=yWqul&goyiN^o!PQ3l>FCt=m$ z0Y7kkd(D`8TuB1AZk@{=EstE61mSB4W6byWkHzxt_h5P>%*V}S#l`i5!aJzTt{^1> zZq#RlrL)@3J#yG}pmH(1i^3BS$d={=t327|C3Lr_0`b6QN-CABq}(>q8n$LjBNaxY zNfjD}&DUCJWutFJ2@zI|)_UXCmaYFfSa;%B3s0AgxXz+o5k4yHzES=o*CWLvzEv~J zhVBWib@n1dlz9TLDNULEFB~`v{4O8gc%2_UNDXSXSEsF(a-9JL5Fxnh?c+bi9ILnQ zyxQ()m6tEM2wLEC2#B-=>@NZ)9xnTfBh39|NZN{gaBD6oL+S zqBh>(v!k!i^@$zqZR4@?i$m3z<`2z&F7U%@N4(MW5# zb^CHO+27%=QTPUua6sH&?DcQ|l7A-`_he%}z~`!pib^0U56*3CQOo^7+wHg4@JL>e zSIvI`>i^MjiGt*7qOkj`z00e5O=Gu-dFijeMG%5b=EV#s}|T{qmpA_ zS|DMXG;Q&0_`nN=3)@e3kYAF&BL3k?_SjZ-3X=z(PLXv~hibmf6_7$OiG3Qy5WWihi z31p8mpNNFsk(%^q1lby_i74quef?z+0*kZ_5I-K_{~MDDTGWe-=*3Ftl<<(Zi%87b zc=Vz9`pqxnd9B9>3r#Ier@YP8i;~>-cMhHQA7wuw2)fA~npFPIT8b=K2nV>}603c* zsrEP@iV7ze&T!Hel}g(^LXdZbrm{o!0z}>>yQCgIKWKnG4W^Q|;aV(%gJydu6(I@%M1POCxI&r-wP(fVCR{^Yr8#r?O%mJCD-skJGL`13(zW z^qmc~>oFY>&Vk!CSZiecJE<`JC95mx*Y9pjvu#(r^t#$o_Iqc2PH(8!JbzO?JjOQ` z!N&Tq$2J(7m{F|s{dpn}_RltpkaVRRTu34oO_wFEO$)iwnY@l=a~w5g3L+xfR%M1K zSzEb^BU#2AS?Zs0Ks3!*!|h#d|C(ifVF8&pTxm_JR6lf^Soup4y5`+4}_B)eA~u!n<8?KKzjA{y40vD+RXgvL~hEO1;kP~+1(ipQi; zSSG}@PBG!ZLCQXx%a-LQqXE&5=+L#ecL;{M-OVK`!^QZ9?9GL5D!su0-I4pdnb+jNWri{IJC|zLG6A8a05wp?D@fY2`i-k6xBXcVDVm#5 zF~;_#_U^8KG@e9{C+0~~J}E@25MxI6TP;cVa%uY`dga@9>4<|!c_MZ8fvwLc(r)_- zNpp>7cf1&n-1thk496ctayVMgnRhRfs|CN=hr*Xnq{L6-U>n&Pi9 z@-glZgL@ zZYh*nNOGxPA;5^g>6_wC@qOxYngWAL5!ufAg3ku|&?UZa?pXOYW_QBgrVhf8F8mDGT$DDlFtXbu8rIyA4GOJc<`{T$!e;p? zf>%MReXqLaGP2WeVSca_6WB<}3a9l+q4M&I->Vymuudlslf-hH`*Rw1k4-1K-|y5C zTxL2@tC8=oI})RmjScf}W!M0NRVs2(oA4u~*SNI5yDph4R597NvoVXwqQFw_y8C40 z7tk7HUv00C&QRyjrjz^wi$TtvOxsxMPn z#;vL`SWqzoK^?eG831CoWZSr2OB7Mf2HaVod-vwz1)35S{A# zw|Pucnv9XTui-y137NjU8M0UWV8=|x?!LwQcq5m~h5!Vi5Agu)$z_5?2*<8RH*T!uOE`Q`1`TEtX5#{qIc zETGT&w8)H}GM~e+IS>RxE^F|50J`A!oufIcpDUhtw#ey@1P?hU(zaM$Oi+drH8P^J z7%juzZQH=Wxf&@rE9Q+|UC7usHqt_l?k0T8o*jgC2|}(6qhE71E6_lBpdsOxoKD);L6ed3}WY%iRG|Hj2Hq)@olGH;&g}ocH&DK$R)< z{obsT9dZ0Rv}U@eTIs)X9a!U@98FSQ#N?MWxj22f?eC^4c{a-r>*9l|x2{Yey^N3} zS1(qeC(D+yvxf&X({{pZRb4!F4UA@gV!Q)&7I6fj?*VE*jV1WYXk_fPml_WQyqN~_iK~Q|EpILkSNQ;ps>1%>c)Oqt zjE+Lk=}uJwpRLJ{DtB#fQ@j@8!Z!k?t79{XlE}(SLu*APn|*@s9z;NX(wd1LQ{n0E zR7V7`MkInO z#vc8Ua$8GyO`_c*5xAUCFDRB!?l>t{v~`Km=JESIyR^WV z*UakNyca`Dc%lwJ?N#L6;xAhI0cB=~=1ZD)zmpHZa86B6B827pxz_wfva zMk(U*MFI3Xd!BL+PRKFpWcy3!=S#1duXz_Y7Ka1$n?sX8sAe>pBUs6J@MiCwG**6& zdTx%(HSG!$u4K1>m+sFGCB;gK;SjV}_a!A$yqq!heslMP2e20cBT@tROeFJUp8H={QZXdZnl% z8J`0Nqe_l=9Jl#3kP)Pt{`lk=7VnA#HDo&N%JQtRVNmjOn_o`Ji+s#{>?^!BvbA$7 z_uhSY5CYlSU8=$ZEhUK}LitQ`#C-biv4)auWWy8J7r&|FpzW(FlwypIi>S%^nMBJq ze^rJaK=s>02{5Nv?xjjcSZ*$AuFVdijO91VS#Dofp}UH7E}Hv;ri;8F;D zK{7@$JNhQpZ+zz-soF`~*gXxwn8;haob8vYM@F;`idDV3uXT8Mcr4E$8ShBNipLQ} z3CRFyS#vCRUzb@p3K1hQ?8UWZ+K|jnt=M(XKY*%8ih8jH7V?Q~xpC+QSR$=A;_^qy zFjiWdtdhTTsQ4YNp+Sf-<}SF(7xT^vE3=hb#8GyqzMe&%vJR?`||v}!|GJfUY(Nc zn=-qFr)vv+DkB9>pWO$P<;!3W{iI*r&!NhwY3;EsuxhDcK$y{NDX#LEs?V=|9wy0^ zFTW9V?!Bw|3>0XQ`F$LQ?VQ|-!IaLoET!MN;NvHidn#WkbsGI_?c**DDwHiFHN~uQ zo$wX?W+bN>B~|1VA`cjgM)}+R0Q1%~=BC%TJv3(CXUaUDutR0HL&hNR@t>E;*r0A5 z+uwz?*&g~ULLHo(TigSxut5MG`Rg|Ddy4i)Jm~$L_{Eo%icoBMWh3;qZ%=D?(%y1y z2q5#rtQK#@jb!3zvNihCWe31SNI=5M+U&R5>gt~rUrMbTokkwvi|q`H?CMA{^vQ|yH29YC7>n|z$H3OXe+va!m& zU6D7@(BgI9pP!&H;)BZMri4OeH0D4E!rKgZ1LSt zr0USVda5)*ASpi%z`R2PuK1Pi*Nc@tSscX`EBy%EyT)fbY&WWX8~dYN$~BA0r0b=` z$^=L53$3yWsEOgMqSgn4YQF1<;Qm#V*;y1MXoHx|Y4i)rpU%#2lO zB@dU`iDCEMTygT#>o2emE{CG&(p%*vC#epw=7k$+=aZS1xfkd+L!2W_`!bkSrU(i| zfxp86hN8Iwyu5Aw?2O|IUcqXI14bn42#^5o3ppW z1I{tj%!H=Oj9?;lc^CZfX_B3S=0H{PQdcOo%mHI~z~G#LTwIb`fWe`OC|(QZ4W{S2 zxQngGGKuHeN*}zJr^Wr6K`;Z_nO4jgtxnZL+Ao_#;d;_AtHc$s1avy-oC}Oob1dObQ(aJ-@T+`gir;o!` zDoS}QaaAHp<0=^!S(c64uax$(W`>ET6KUZT6JmwwKIwbBVli^Mba;agDu8@h4_ARl zG<@q%Us1m0G2NL^yR0s$_B73FcN3JbKg-&!DD$%6h_7v7o(NT?{*%NE<+K)_E$DFB z%m!aZ5Cwo-3M%XmDQM&p*0kt|1S_IgwC-mQ7JlS+;X##W02sUvNHa^)h!!Z}B~387 zWY`jY1=y8H+W|=UG{~NCDFwp?k)Vz0{tMB|f$c8iTUXSg7(^FA76$W^DKz>1(RhO9 z27|_fjW6(#e3GOOqCp_kWF`ffKC{^NN@8SdnbpPRt^$nhWF`p#tUAE}4)2wuT5rgK znn3qZTH`8FCo?Y4(*{mRfqYc^8l%$^KI0HDeJsG|HppZlaR zcJFgIvorVb@KF?@eZA!}8%F>JWPE~iIYd}35>P3cP5hEvZDV(GnnrjoFlKjokKkBP zsQvmufrUg`m`NM2;s+6nhIpTaCLRR^)VU8-;R6~hf<}S3p%E(4TxaFJx6u;tFkA|s zwmEiX{a5eAWOGHy5i_EhGn(BiYE zG+X@*nQQ=>3X!zN01gADD!IqPWbBb+XJ3Yj}@ zc`Ms**IJ~uL`+J!@q`C_grbT1jjU+?{48{{rGB0f*%BGXk>$(kwl z5{_9PK+yPu*BaF|s8M1m;yni7wtCPMTN_$0GucNe=;R3!Hl4-t(iMAe91@0FJJ56l zlk$ru)sS`H%2R~a6w|TbT`a~Dobg^Ez-uWmX!gInO7!+&s&t9-W)9i&n|LJW<7~JQ zV^4v1T?QqFfnUs1&)lQvIPfn%1zicW3W>y3Ft{P;URq3^v6rv{fak!jcB%=1M4G^3 z$fpn|`mV~Ua`$@GAW%5gZ;J-bU_mT6K~Uc~mT*y9#1pY>oLJI$S;@|N=Id>nxgS!m zF5<1(9QjG=JK>~(#6lN3Iyyj=C2i=Jfia*{&A3+ptlbuL0m^72c!@u6|jqt$!#XSa^0EzKg?-%!dy}kwnU5W8vI!~C{Dkj^zyH7FW z>;1WR`nwGKgWsr9bCW_BOs}HyF+XhN%jnK>sR0g4YH z;h4IBi5-y0+e8;dnm_$Bj51ZUv^x26z-0bJ!bn0{McULCMfu2gAm)@NE6TN1K1T8A z$y+Yxy(@{PRKvPZfmrvq$jzn)Q9O0VzTBJZJ`1M9C#hl@23Q)uW9<%hw2&TOBvd_f z=$9!27E)j8C{5Sdeacfs{dDzWh(1&(@wK4I|L-^)l^p z^MKkbNy@&3LR8;~TIk?t54}}?Wv|@A0?Nz4UMWKR(~2Vs3e1326R8#{s~C|6i2px8 zuWbY^*n8gFNslrf9`W8Z=Tf21itld>&|$3Ur%X+t3zqojzGL{qF+nRqM8%Y@U8JD% z=FG+*tvA4{7vhSQx=ZgY9@z4*rAslK0hS3U$mw5f=B{_w9JxQ3pQ6P_onbUGVK=gr zeaT-NJMPTjg+Mr-J^ji_CWP2BRS0>MDY|`i=6eAVpWWj7ZmM^6X8>|F_Hax9`}wHj z^YCJ<0dT0y+{g2wGu`IK%6$;Cj!pau`>IS85l47eE=TwpfOU|P(%J!M7BH&_y-Q#p ziewtOSdnM-?f!6)gy?(0a9V{(GOIkR2QhpwQTYJQvGxx<46xFHj+9J4B%^y^y3aGyH+)VVW#`t7;P;7N{5Z_p7$O;pyX( z_Z9L3Z|`b3+J0)9BGkrc^gP8yfyrBn%Xqx5@r0tK*94e}Hz|MkxIY(;dpL1Fm@LR< zw!3<$QGLQtRNEii{k8k}?jl9#B*3vrd*NRKV>mwQ&t4xcYIpCFrXw=A)cG1$6+p5B zkvFNG`s=c=u_kwFcU5*j*&XS423t7kC3zfi0497bH8mVA>P$DH2ij2dD&vJRoAZVl zsx!1NSG#e6zjz>rf%<|2DCCcUjxtc+k_0tTwftnga`!@sU&nc3tXSz+(4But4igX( zqzm87`CT8FCTVQZH1n{Jp>R1+Rkm9E!CO*N@=@+F4Lkew%b0|9pzQ}49tpy&>9_LZ z`ycRnkBc6lK7v^^YYyXW1t8D08)WZ>ikzZA;iOPHl9MD7s^r@9yJci8uW^lR@ZD}F0aihgFJCAnVF=8d1-0dy_I@eyW9cQYWt(i7PC2w)(7-+ zK1`E0-oAJT(tQiHa?>kFQBeFDoBc`Oss`0AHT!PfqG+MUrD6IEG!GDuv7H0B-)lHT zcg!3l*?43(0s%6mqp}5UrMQC^f(ERZg_<%Q5n`$d+;7kk>qjjUrE30}J^Vw!?QncN zrEyTavrto~yxkaMUhf_GR)YrsZ|g6vA4{)M_S4*;~=G6$oR zG&E5i9pNbU!P=8F9>BvWbVzGH!hR8gJWEZEpCkMOCMYw8u%qaiB^XY+g}o{UI*DPq z+v?@tU7Y`N9ykXthKLVMTC1S>Ex9jEc71DPE!cz|+H_Geo4;4Sw*7j3_T}}82f_pS zux`at1Ps;@J##27A(51)uBU~2j@NoJ9LWT*hdPi3JaUyIZu#y73b7AJVlhCcEyatl zi#mpeKJuD;F@`?cFV4JAe93rAOiUIx0ZK&5XSWwUmxFU=v%Uw^6>?6c{a;UXE0eKK zF$W5ocg0&k4L@07g>=dX)RtSdR*M}z_%qg@J$d*j6+tjyI#MoSExzdYY3sZ0pG3m% zB2ufe6O@m?`$9?YzH3)!+|{#htyHb5-2d8>oe}?SuWLe5d(T<=?9ECqQAXH0b{A_! zq`%?grTv`if9w~mZS65(oBH#TlDiHLjxOJq+(G5g7BzbF5DiF(YvIPiU~TI+naBc- zO2qbu;;?IfP#17eIy{~i(SnFn1N{!mq4j=)y;0B3EIR~}CL4Rf+1c4FBQbtW`k9Y%AkOXTWyPSQ%M-Gzb9PP5+3E#2vHY88cI=)l1~&Q1a~^)9bN#F zUey4z9vd5bj+%}DS?PmBA;Giu3-Ztlgqd}LvmE-ot%;2Xdy{Nz;pz!Xx3n*9q2E%= z*Y^zy@(K#V)~a&DuEKMRX0>{W#C`4izysKp2V7>|15Jp??4cqE-QAg#Mj*>Lor!{y znU698(O1Dp5Y^+@!^1;AUpgJs<+iB62UxKDzCF+$U2g9Jr0AAY>we=_Ykmaipl{Y% z@e8BLoESUyBfgI^RyHGGxuwK+iSbBu1nzqy)>+pg3!;DzSVleXdvCUy4?uQwd!m(A z?gA#&Dyj?pfZug)_Vc}t8g+Y=t3B#w)cc7fmfO^Sb-IH}s{2l+uz}zM;N}RSs=gM( zpMQS;rQ&oK27!d3#9fX~E9)+I*64GvN@Hkv`h$cEz#scqOBt4wl|g7|&;WQ6Te9eM zorJ^>)E4wWx7uC1J>oQl#10>92s>N(6>W{Zm!9<44G<5h z*iPTNfGWn7M!zEXO}gmA6jSha^tsy;OK$OZPmW$h*-;6pfk!f_ea({K_#W>=r-t`I z)6L}koo)(H)vIf4q|%r7-M~rk<$M81%8ArdhLmwxq4F>wF+Zt%n4N@l6{kV*;hCPP zUf0AeWQDK$3r^#2d!{0c5C}D@hd<55Bmn`kt^9auGd5@< zgtKQOV6Dx(t3&o?@Ii9C5GblmIn4zcjtTv)p`YtG>|~o~-6vm=FnSU$rwe`(#Tg*8 z-1@3>cgpfpO#u}|=7?_Fp}>2Axjx{%Y8(3+&8CTuJ2B$S-SxH1v?1&SD9(m^dLl&H zV!Lw8`9UcBGsTBSSY=s~3kVg>D6SgXP?WDM%7JiFCnagn{d^=ESpW(dk~x{i(pcub zK?^eCX(*2!yTX<9bb;UsWSbDASnNfkoAKScr0+(&Ml!kXuGa(Y8vXnPg`a|&m*r}&+(aMA*=^VV_zTr1`g(IUgXg|~pbB#Z zO)qay?RCd{%X>seRA#gI5;gIPQa#J%3(3I}RKS#Ok-5$Q%f8)=uGaH&}30>HGJY))zNHBGWRcv6Py}mn@ zTm(q@3)QU{s|(mMxy6l+(pfMEg%N+<7uAuGTXlfylmVeg@}$91LIts)0Vg5!>3h{7 z4iHqKf>uCcz$g(=XqJv-^1~uwzXe8z60{Q7Co{8Y*M}2UKu)7GI^v4zRT{Wm5H#Hl zY>|Vb)6a#WV%1^`axizA48|vo=6ZYjtu<>4(P-r2J-cBtwH2xSpilCdo}S0YkE{+3 zPHz>HL>m%Tz*xsMcK%CTroDA3lEJKqo(B`OSH5dz>J9c7qsFcmM=CH`;4C2j-L6Pj zNtW;*?<22DHZDj5@c7$Itwa-3ZAd=Fk;#d~K_<89I{mgGX)rf859(W7o@i_`ic+#o z0{svuq!o;wJp(-$#O!9i%O$4Yv%F9ZC7>Zgz;Wf$P?1F#V;oewk__N$o~{PYsBs)H zyfn7)W~l$8&P50nM5#UXCWVlTZ+jfK-a=U+m{5#j3un;NLR|_PlqKEW2tz6Ej-R{e=M0#7b9Gv&`eoLH$4FL!XXCxeROL zu!yoVQCJcTIKF0h%rH?H0OrX-izDor0%*}P$pMowjdgWnz)LiRP@%U(GBSD;p-LvF z9TA7{KRY7&DGMpxsP?W^@{{%DXoxNF6?WyrWB{Jv1>m2(ZHQMu6MFbFglpQlbC{1& z-vu!2Odt{4{?@3V^v(!xB8#)Ypsi+|v=23*sc+x(O1&6<`*F)7quwl%G}(j;=#uCp zEfVmGiV8s-az0NGH+-17Ss4IK)v>**4^0vPqe+&QmS5~gU46iGYV&KTwCc)KJLnn3 zNE_cZ0chVe@0~m1k-4DJ{|o$GqM5ytHx=3k|LbHPq&yaHwLF}N*&Q(E7v}SHkyo>} zj4%7Us9XXbR}#Qkp!hsd(HE4Iwzu0dO0{9k6iTCDvUhMRQ6!(R3izU#p1iOhInmN9 z^8*F%A7zBHg~JC+t|+I;RtF0o_<|;ERDmed*_<6%_hS>O${v0g<>I*7ERxvOnDUsU z1-l@3vNMWOGMFraqlt{q<$?S|I%!WM2){f*s9`poPXJoELu}WQ?VZK?(LfU# zsaPqUpX2rlwbS|{HTrY^St1GSvRiZYVB&`HS6}@+7dfBJb4`QKn`lma`qp1SQSZG* zYW{qL3VvZB3!WOP$-VoA_loDXDxJd1JBX)sqwa^~LX|>C#ratPTSs0Lao(IY&*H_6 zA$W-z?6n{H(Lf%sT~~ijj%~LEbcuEqs3`Eh{l==`h$=CTDdl|(#TZSw9~?@}D*F4= zvrpzjPa>Nxfr1c6)%-{W>2C5y1&_iuaANNtmR>dao)Pg8pPLP=wL<}|2qLc&M|Yz;RnAA92ack)zj*>mA?MbbzgaT;^sHLkhjs6}FrRun z%ciO4wLUm04u0dOSN^W9eXsU`2C7-9fA&f`)wj2bEC&3qMljG7Iie_@0h5W+>3Oeo z*Jbjf)aGj8X*iJNQo2ISZ9~jwJNvGG3Vb4=>79XW;tO(qyJrI{Fq+}cKHZbJzzh23 z^Ow|EOo4KN8$$xX_r&x(Ke+fB;&>H{guCwD?)KBu2!z$R<_;*@)V8T{5FB6m=*^(5 zVxjcowYhJts4MY);=R{O+37pr%OFDqB|0PVj{O9cG6yL@C?M?g;A0!&!1P9%smYcl z=GyK~nTOjz{XV-7?+?nomL@Tfv~2qcK*~FiY$u1q+rY2}NCs2V+W}-+Tp!`QIq&uP zGr#;BA=rv5b&o%ik(Jpf)&xDdyYGWyA})?|3b*WH^{P(y8f zvH>ly9%#ai>`5wDElM%BWhTvsh< zL4gz~uC^ppIIrIa1LZIhOf?@cWQ|8k8UQ{8@wD*@UI+uqtb4eVc`8fM)(86lNomua z^bIMoTeu10;aHga6@ou7O?Of7N`tkgF4V+4Xfb_kr!W4_7JP#vgiGehr9e!@JyiS! zX0Jo*5-1YDD2heozH1OkHHSWS{!>b_nN@Mz(r8<+7eE?y+B3PL1z_w@t+Mx9M-pNN z=E}T5gPYE3WV^wTp{U;gUmBWxJuuyQPVFWdRc_>9oL+v$ph;HzTxSF$(GT^q6wn7X zGn~#N^8|$U00Bw@T`Z{Yh)5NozNcZJoG&gV8+-Q(tR;eNNT{j-v@(U9?<{@CVbYuB zXFc6fU0;th<(8ni0dg+zX$IZR#bJbWD;ONIR;0y?X-kwm@bpPXmyZ|F2?YQE_)NrQ zd7Qd&>VQ1^Q7!P92r#l{`rR`71$XvPw9SqNO2j_=C`ITE585`x3Fm@OU5JaX73OGF z`g;f69B*hR)&A_)iTXxM!750Xz{G}bP2ClPQ64-zB~bv7q1rCPstk`l&XI+q83fR< ziRN##gag8OcwN)|Lj!vC{sczDY3tK&oLwj~L*hXk2tp(f2bzWK|34E3ZnX3J@-gGf zySviM&(HaHr(aD9n4KbhfE5LBDR5u0m$2Z(R~@cTk4)K-q!nKbYL<3Oi5u_%M=y^6 zC(1XzCMUJE7Z^| zK}Tx0soCpRkU^oK%~TcD)u7=!t-kh-jxWp0*52ay!0!ND6@9ku6R2$_1&;}NHb@C?g?Fu zUHuq1TD8i?4R{=j-bRCfBkwqX$3Yy*xh(e&G&&fx_R-O<PqYk_@E<&gS4z@uCIKtr$>54nQ&Zvp$9 z>sK{{8gafSrWym!t9acm8ip8O@`dv{h+%Y6WZdK)fbM9 z8#jKMzazbHmVLaE(q6CWX0kasIZx~*jnj^R+Ht_9=izJD#Lmt(Jr3N_vOzyfzU B -> C -> etc.**") and you want the user's authentication and authorization data to be propagated from one microservice to the next; + +== This Guide + +In these guides, we work with a simple invocation chain composed by +++two+++ microservices: + +* **Microservice A**: acting as **client** +* **Microservice B**: acting as **server** + +Our invocation chain is then: "**Microservice A -> Microservice B**": when working with https://microprofile.io/[Microprofile], this is achieved by using the https://github.com/eclipse/microprofile-rest-client[microprofile-rest-client]; + +Specifically, **Microservice A** will use the https://github.com/eclipse/microprofile-rest-client[microprofile-rest-client] to invoke the Jakarta REST service exposed by **Microservice B**; + +For both services, we will start from the microservice we built in link:simple-microservice-part1[{simple-microservice-part1}] (complete code in {source-code-git-repository}/simple-microservice); + +== Microservice B - the server + +We start from the server because we need the server's API for the client later on; + +=== Maven Project + +Copy {source-code-git-repository}/simple-microservice into a new folder named *simple-microservice-server* and: + +* remove folder *src/test* +* remove all test scope dependencies + +NOTE: we remove tests in preparation for the upcoming guides + +==== pom.xml + +Set `simple-microservice-server`; + +NOTE: **Microservice B** is basically unchanged, we will modify it in link:simple-microservice-client-part3[{simple-microservice-client-part3}] + +==== Build the application + +[source,bash] +---- +mvn clean package +---- + +=== Docker Image + +==== Dockerfile + +Since you copied {source-code-git-repository}/simple-microservice[simple-microservice], the Dockerfile from link:https://github.com/wildfly/wildfly-s2i/blob/main/examples/docker-build/Dockerfile[examples/docker-build/Dockerfile, window="_blank"] should already be at the root of your project; + +==== Build the Docker Image + +[source,bash,subs="normal"] +---- +podman build -t {my-jaxrs-app-docker-image-name-server}:latest . +---- + +==== Run the Docker Image + +First we create a network for our containers: + +[source,bash,subs="normal"] +---- +podman network create {podman-network-name} +---- + +Then we run our container using this network: + +[source,bash,subs="normal"] +---- +podman run --rm -p 8180:8080 -p 10090:9990 \ + --network={podman-network-name} \ + --name={my-jaxrs-app-docker-image-name-server} \ + {my-jaxrs-app-docker-image-name-server} +---- + +== Microservice A - the client + +=== Maven Project + +Copy {source-code-git-repository}/simple-microservice into a new folder named *simple-microservice-client* and: + +* remove folder *src/test* +* remove all test scope dependencies + +NOTE: we remove tests in preparation for the upcoming guides + +==== pom.xml + +Set `simple-microservice-client`; + +Add the following to `dependencyManagement`: + +[source,xml,subs="normal"] +---- + + org.wildfly.bom + wildfly-microprofile + ${version.wildfly.bom} + pom + import + +---- + +Add the following to `dependencies`: + +[source,xml,subs="normal"] +---- + + org.eclipse.microprofile.rest.client + microprofile-rest-client-api + provided + + + org.eclipse.microprofile.config + microprofile-config-api + provided + +---- + +Add the following `layers` in the `wildfly-maven-plugin`: + +[source,xml,subs="normal"] +---- + microprofile-config + microprofile-rest-client +---- + +Later on, we will use: + +* **microprofile-config** to make the URL to **Microservice B** configurable +* **microprofile-rest-client** to actually invoke **Microservice B** + +==== microprofile-config.properties + +As anticipated, we use **microprofile-config** to make the URL to **Microservice B** configurable; + +Add file `src/main/resources/META-INF/microprofile-config.properties` with the following content: + +.microprofile-config.properties: +[source,properties] +---- +simple-microservice-server/mp-rest/uri=${simple-microservice-server-uri:http://127.0.0.1:8080} +simple-microservice-server/mp-rest/connectTimeout=3000 +---- + +NOTE: `simple-microservice-server-uri` would pick up its value, whenever set, from the environment variable named `SIMPLE_MICROSERVICE_SERVER_URI` (see https://download.eclipse.org/microprofile/microprofile-config-3.0/microprofile-config-spec-3.0.html#default_configsources.env.mapping[env.mapping]) + +==== Java code + +Add the following interface: + +.GettingStartedEndpointInterface.java: +[source,java] +---- +package org.wildfly.examples; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(configKey="simple-microservice-server") +@Path("/hello") +public interface GettingStartedEndpointInterface { + @GET + @Path("/{name}") + @Produces(MediaType.TEXT_PLAIN) + public Response sayHello(final @PathParam("name") String name); +} +---- + +NOTE: this class is used to define the API to be invoked by the Rest Client; the actual URL where the remote service is +located, comes from the `microprofile-config.properties` file we just added; + +Remove the `src/main/java/org/wildfly/examples/GettingStartedService.java` file and replace the content of +`src/main/java/org/wildfly/examples/GettingStartedEndpoint.java` with the following: + +.GettingStartedEndpoint.java: +[source,java] +---- +package org.wildfly.examples; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.rest.client.inject.RestClient; + +@Path("/") +public class GettingStartedEndpoint { + + @Inject + @RestClient + private GettingStartedEndpointInterface service; + + @GET + @Path("/{name}") + @Produces(MediaType.TEXT_PLAIN) + public Response sayHello(final @PathParam("name") String name) { + return service.sayHello(name); + } +} +---- + +NOTE: as anticipated, we use **microprofile-rest-client** to actually invoke **Microservice B** + +==== Build the application + +[source,bash] +---- +mvn clean package +---- + +=== Docker Image + +==== Dockerfile + +Since you copied {source-code-git-repository}/simple-microservice[simple-microservice], the Dockerfile from link:https://github.com/wildfly/wildfly-s2i/blob/main/examples/docker-build/Dockerfile[examples/docker-build/Dockerfile, window="_blank"] should already be at the root of your project; + +==== Build the Docker Image `{my-jaxrs-app-docker-image-name-client}:latest` with the following command: + +[source,bash,subs="normal"] +---- +podman build -t {my-jaxrs-app-docker-image-name-client}:latest . +---- + +==== Run the Docker Image + +[source,bash,subs="normal"] +---- +podman run --rm -p 8080:8080 -p 9990:9990 \ + --network={podman-network-name} \ + --env "SIMPLE_MICROSERVICE_SERVER_URI=http://{my-jaxrs-app-docker-image-name-server}:8080/hello" \ + --name={my-jaxrs-app-docker-image-name-client} \ + {my-jaxrs-app-docker-image-name-client} +---- + +NOTE: The **{my-jaxrs-app-docker-image-name-server}** container can be reached, inside the **{podman-network-name}** network, using the DNS name **{my-jaxrs-app-docker-image-name-server}** + +== Test + +Open http://localhost:8080[http://localhost:8080] in your browser: this web page is served by the **{my-jaxrs-app-docker-image-name-client}** container; + +Write something in the "Name" input box and then press "Say Hello": the response you'll see will come from **{my-jaxrs-app-docker-image-name-server}** container! + +The complete invocation chain is "**web browser** -> **{my-jaxrs-app-docker-image-name-client}** -> **{my-jaxrs-app-docker-image-name-server}**" + +== What's next? + +link:simple-microservice-client-part2[{simple-microservice-client-part2}] + +[[references]] +== References + +* https://microprofile.io/specifications/microprofile-rest-client[microprofile-rest-client] +* https://microprofile.io/specifications/microprofile-config[microprofile-config] +* Source code for this guide: +** {source-code-git-repository}/simple-microservice-rest-client/simple-microservice-client +** {source-code-git-repository}/simple-microservice-rest-client/simple-microservice-server + + diff --git a/guides/get-started-microservices-on-kubernetes/simple-microservice-client-part2.adoc b/guides/get-started-microservices-on-kubernetes/simple-microservice-client-part2.adoc new file mode 100644 index 00000000..b1f713ef --- /dev/null +++ b/guides/get-started-microservices-on-kubernetes/simple-microservice-client-part2.adoc @@ -0,0 +1,293 @@ += {simple-microservice-client-part2} +:summary: Invoke one microservice from another on Kubernetes +:includedir: ../_includes +include::{includedir}/_attributes.adoc[] +include::./_includes/_titles.adoc[] +include::_includes/_constants.adoc[] +// you can override any attributes eg to lengthen the +// time to complete the guide +:prerequisites-time: 10 + +In this guide, you will learn HOW-TO run the Docker Images you built in link:simple-microservice-client-part1[{simple-microservice-client-part1}] on Kubernetes. + +[[prerequisites]] +== Prerequisites + +To complete this guide, you need: + +* Complete link:simple-microservice-client-part1[{simple-microservice-client-part1}] + +== Introduction + +In this guide, we will deploy on Kubernetes the two container images we created in link:simple-microservice-client-part1[{simple-microservice-client-part1}]; + +== Minikube + +You can use whatever Kubernetes cluster you have available; in this guide, and in the following, we will use link:https://minikube.sigs.k8s.io/docs/[minikube, window="_blank"]. + +== Microservice B - the server + +=== Image Registry + +To make the `{my-jaxrs-app-docker-image-name-server}:latest` Docker Image available to Kubernetes, you need to push it to some Image Registry that is accessible by the Kubernetes cluster you want to use. + +==== Quay.io + +There are many Image Registries you can use: in this guide, we will push the `{my-jaxrs-app-docker-image-name-server}:latest` Docker Image, to the link:https://quay.io[quay.io, window="_blank"] Image Registry. + +Create a public repository named `{my-jaxrs-app-docker-image-name-server}` on link:https://quay.io[quay.io, window="_blank"] (e.g. link:https://quay.io/repository/{quay-io-account-name}/my-jaxrs-app[https://quay.io/repository/{quay-io-account-name}/{my-jaxrs-app-docker-image-name-server}, window="_blank"]). + +NOTE: replace `{quay-io-account-name}` with the name of your account in all the commands that will follow + +Tag the Docker image: + +[source,bash,subs="normal"] +---- +podman tag {my-jaxrs-app-docker-image-name-server} quay.io/{quay-io-account-name}/{my-jaxrs-app-docker-image-name-server} +---- + +Push the `{my-jaxrs-app-docker-image-name-server}` Docker Image to it: + +[source,bash,subs="normal"] +---- +podman push quay.io/{quay-io-account-name}/{my-jaxrs-app-docker-image-name-server} +---- + +At this point, the `{my-jaxrs-app-docker-image-name-server}:latest` Docker Image should be publicly available and free to be consumed by any Kubernetes Cluster; + +=== Deploy on Kubernetes + +Create a file named `{my-jaxrs-app-docker-image-name-server}-deployment.yaml`: + +.{my-jaxrs-app-docker-image-name-server}-deployment.yaml: +[source,yaml,subs="normal"] +---- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {my-jaxrs-app-docker-image-name-server}-deployment + labels: + app: {my-jaxrs-app-docker-image-name-server} +spec: + replicas: 1 + selector: + matchLabels: + app: {my-jaxrs-app-docker-image-name-server} + template: + metadata: + labels: + app: {my-jaxrs-app-docker-image-name-server} + spec: + containers: + - name: {my-jaxrs-app-docker-image-name-server} + image: quay.io/tborgato/{my-jaxrs-app-docker-image-name-server} + ports: + - containerPort: 8080 + - containerPort: 9990 + livenessProbe: + httpGet: + path: /health/live + port: 9990 + readinessProbe: + httpGet: + path: /health/ready + port: 9990 + startupProbe: + httpGet: + path: /health/started + port: 9990 +---- + +Deploy to your Kubernetes Cluster: + +[source,bash,subs="normal"] +---- +kubectl apply -f {my-jaxrs-app-docker-image-name-server}-deployment.yaml +---- + +=== Create Kubernetes ClusterIP Service + +We create a service to consume the services exposed by **{my-jaxrs-app-docker-image-name-server}** from inside Kubernetes; + +Create a file named `{my-jaxrs-app-docker-image-name-server}-service.yaml`: + +.{my-jaxrs-app-docker-image-name-server}-service.yaml: +[source,yaml,subs="normal"] +---- +apiVersion: v1 +kind: Service +metadata: + name: {my-jaxrs-app-docker-image-name-server}-service + labels: + app: {my-jaxrs-app-docker-image-name-server} +spec: + ports: + - name: http + protocol: TCP + port: 8080 + targetPort: 8080 + selector: + app: {my-jaxrs-app-docker-image-name-server} + type: ClusterIP +---- + +Deploy to your Kubernetes Cluster: + +[source,bash,subs="normal"] +---- +kubectl apply -f {my-jaxrs-app-docker-image-name-server}-service.yaml +---- + +=== Check Kubernetes Service + +[source,bash,subs="normal"] +---- +kubectl run --rm -it --tty curl-{my-jaxrs-app-docker-image-name-server} --image=curlimages/curl --restart=Never ‐‐ {my-jaxrs-app-docker-image-name-server}-service:8080/hello/pippo +---- + +== Microservice A - the client + +=== Image Registry + +To make the `{my-jaxrs-app-docker-image-name-client}:latest` Docker Image available to Kubernetes, you need to push it to some Image Registry that is accessible by the Kubernetes cluster you want to use. + +==== Quay.io + +There are many Image Registries you can use: in this guide, we will push the `{my-jaxrs-app-docker-image-name-client}:latest` Docker Image, to the link:https://quay.io[quay.io, window="_blank"] Image Registry. + +Create a public repository named `{my-jaxrs-app-docker-image-name-client}` on link:https://quay.io[quay.io, window="_blank"] (e.g. link:https://quay.io/repository/{quay-io-account-name}/my-jaxrs-app[https://quay.io/repository/{quay-io-account-name}/{my-jaxrs-app-docker-image-name-client}, window="_blank"]). + +NOTE: replace `{quay-io-account-name}` with the name of your account in all the commands that will follow + +Tag the Docker image: + +[source,bash,subs="normal"] +---- +podman tag {my-jaxrs-app-docker-image-name-client} quay.io/{quay-io-account-name}/{my-jaxrs-app-docker-image-name-client} +---- + +Push the `{my-jaxrs-app-docker-image-name-client}` Docker Image to it: + +[source,bash,subs="normal"] +---- +podman push quay.io/{quay-io-account-name}/{my-jaxrs-app-docker-image-name-client} +---- + +At this point, the `{my-jaxrs-app-docker-image-name-client}:latest` Docker Image should be publicly available and free to be consumed by any Kubernetes Cluster; + +=== Deploy on Kubernetes + +Create a file named `{my-jaxrs-app-docker-image-name-client}-deployment.yaml`: + +.{my-jaxrs-app-docker-image-name-client}-deployment.yaml: +[source,yaml,subs="normal"] +---- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {my-jaxrs-app-docker-image-name-client}-deployment + labels: + app: {my-jaxrs-app-docker-image-name-client} +spec: + replicas: 1 + selector: + matchLabels: + app: {my-jaxrs-app-docker-image-name-client} + template: + metadata: + labels: + app: {my-jaxrs-app-docker-image-name-client} + spec: + containers: + - name: {my-jaxrs-app-docker-image-name-client} + image: quay.io/tborgato/{my-jaxrs-app-docker-image-name-client} + ports: + - containerPort: 8080 + - containerPort: 9990 + livenessProbe: + httpGet: + path: /health/live + port: 9990 + readinessProbe: + httpGet: + path: /health/ready + port: 9990 + startupProbe: + httpGet: + path: /health/started + port: 9990 + env: + - name: SIMPLE_MICROSERVICE_SERVER_URI + value: "http://{my-jaxrs-app-docker-image-name-server}-service:8080" +---- + +NOTE: The environment variable `SIMPLE_MICROSERVICE_SERVER_URI` allows **{my-jaxrs-app-docker-image-name-client}** to invoke **{my-jaxrs-app-docker-image-name-server}** through the service **{my-jaxrs-app-docker-image-name-server}-service** + +Deploy to your Kubernetes Cluster: + +[source,bash,subs="normal"] +---- +kubectl apply -f {my-jaxrs-app-docker-image-name-client}-deployment.yaml +---- + +=== Create Kubernetes NodePort Service + +We create a service to consume the services exposed by **{my-jaxrs-app-docker-image-name-client}** from outside Kubernetes; + +Create a file named `{my-jaxrs-app-docker-image-name-client}-service.yaml`: + +.{my-jaxrs-app-docker-image-name-client}-service.yaml: +[source,yaml,subs="normal"] +---- +apiVersion: v1 +kind: Service +metadata: + name: {my-jaxrs-app-docker-image-name-client}-service + labels: + app: {my-jaxrs-app-docker-image-name-client} +spec: + ports: + - name: http + protocol: TCP + port: 8080 + targetPort: 8080 + selector: + app: {my-jaxrs-app-docker-image-name-client} + type: NodePort +---- + +Deploy to your Kubernetes Cluster: + +[source,bash,subs="normal"] +---- +kubectl apply -f {my-jaxrs-app-docker-image-name-client}-service.yaml +---- + +=== Check your application + +Find out on what IP address/port, link:https://minikube.sigs.k8s.io/docs/[minikube, window="_blank"] is exposing your service: + +[source,bash,subs="normal"] +---- +$ minikube service {my-jaxrs-app-docker-image-name-client}-service --url +http://192.168.39.143:30347 +---- + +Verify it's working as expected: + +[source,bash,subs="normal"] +---- +$ curl http://192.168.39.143:30347/hello/pippo +Hello 'pippo'. +---- + +== What's next? + +link:simple-microservice-client-part3[{simple-microservice-client-part3}] + +[[references]] +== References + +* Source code for this guide: +** {source-code-git-repository}/simple-microservice-rest-client/simple-microservice-client +** {source-code-git-repository}/simple-microservice-rest-client/simple-microservice-server \ No newline at end of file diff --git a/guides/get-started-microservices-on-kubernetes/simple-microservice-client-part3.adoc b/guides/get-started-microservices-on-kubernetes/simple-microservice-client-part3.adoc new file mode 100644 index 00000000..8703f3c6 --- /dev/null +++ b/guides/get-started-microservices-on-kubernetes/simple-microservice-client-part3.adoc @@ -0,0 +1,734 @@ += {simple-microservice-client-part3} +:summary: Invoke one microservice from another on Kubernetes propagating users authentication +:includedir: ../_includes +include::{includedir}/_attributes.adoc[] +include::./_includes/_titles.adoc[] +include::_includes/_constants.adoc[] +// you can override any attributes eg to lengthen the +// time to complete the guide +:prerequisites-time: 10 + +In this guide, you will learn HOW-TO propagate user authentication and authorization data between two microservices; + +[[prerequisites]] +== Prerequisites + +To complete this guide, you need: + +* Complete link:simple-microservice-client-part2[{simple-microservice-client-part2}] + +== Introduction + +The scenario consists of: + +* a *Service* deployed on a Kubernetes cluster which is not exposed outside the cluster +* a *Web Application* deployed on a Kubernetes cluster which is exposed outside the cluster and consumes the *Service* + +The user is required to authenticate before using the *Web Application* and, after authentication happens, we want authentication data to be available, not only to the *Web Application*, but also tho the *Service*; authentication is delegated to *Keycloak* using link:https://www.keycloak.org/securing-apps/oidc-layers[OpenID Connect] protocol; + +The overall architecture is: + +image::get-started-microservices-on-kubernetes/simple-microservice-client-part3-1.png[] + +We will start from the two microservices we built in link:simple-microservice-client-part2[{simple-microservice-client-part2}] and: + +* *Microservice A* will be the basis for *Web Application* +* *Microservice B* will be the basis for *Service* + +=== How it works + +This is how it works: + +1. The user tries to access *Web Application* (*Microservice A*) from a web browser +2. The browser is redirected to *Keycloak* where, by providing username and password, the user authenticates itself +3. The browser is redirected back to *Web Application*: this time the request contains a *JWT Access Token* (and a few more tokens actually) provided by *Keycloak*, containing authentication and authorization data +4. *Web Application* validates the *JWT Access Token* and grants access to the user +5. *Web Application* invokes *Service* (*Microservice B*) forwarding to it the *JWT Access Token* it just received +6. *Service* validates the *JWT Access Token* and grants access to *Web Application* + +== Keycloak + +First, we install and configure Keycloak with users, groups etc.. + +=== Run Keycloak on Kubernetes + +Download link:{source-code-git-repository}/simple-microservice-rest-client/simple-microservice-client-secured/kubernetes/keycloak-realm-realm.json[keycloak-realm-realm.json] and create a `configmap` using it as its content: + +[source,bash,subs="normal"] +---- +kubectl create configmap {keycloak-data-import} --from-file=keycloak-realm-realm.json=keycloak-realm-realm.json +---- + +Create a file named `keycloak.yaml`: + +.keycloak.yaml: +[source,yaml,subs="normal"] +---- +apiVersion: v1 +kind: Service +metadata: + name: {keycloak-external} + labels: + app: keycloak +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + selector: + app: keycloak + type: NodePort +--- +apiVersion: v1 +kind: Service +metadata: + name: {keycloak-internal} + labels: + app: keycloak +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + selector: + app: keycloak + type: ClusterIP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: keycloak + labels: + app: keycloak +spec: + replicas: 1 + selector: + matchLabels: + app: keycloak + template: + metadata: + labels: + app: keycloak + spec: + volumes: + - name: {keycloak-data-import}-volume + configMap: + name: {keycloak-data-import} + containers: + - name: keycloak + image: quay.io/keycloak/keycloak:26.0 + args: [ "start-dev", "--import-realm" ] + env: + - name: KEYCLOAK_ADMIN + value: "{keycloak-admin-user}" + - name: KEYCLOAK_ADMIN_PASSWORD + value: "{keycloak-admin-pws}" + ports: + - name: http + containerPort: 8080 + readinessProbe: + httpGet: + path: /realms/master + port: 8080 + volumeMounts: + - name: {keycloak-data-import}-volume + mountPath: /opt/keycloak/data/import +---- + +Deploy to your Kubernetes Cluster: + +[source,bash,subs="normal"] +---- +kubectl apply -f keycloak.yaml +---- + +To access the Keycloak console, find out on what IP address/port, link:https://minikube.sigs.k8s.io/docs/[minikube, window="_blank"] is exposing your **{keycloak-external}** service: + +[source,bash,subs="normal"] +---- +$ minikube service keycloak-external --url +http://192.168.39.190:31950 +---- + +Open the link in your web browser and login to Keycloak with username/password "*{keycloak-admin-user}*/*{keycloak-admin-pws}*"; + +NOTE: since we are using minikube, we expose *Keycloak* outside the cluster with a `NodePort` service (**{keycloak-external}**) and inside the cluster with a `ClusterIP` service (**{keycloak-internal}**) + +==== optional alternative: configure Keycloak manually + +As an alternative to using *Keycloak* auto import feature (see the "--import-realm" command argument above), you can configure *Keycloak* manually: remove `volumes` and `volumeMounts` and follow these steps: + +1. Create a realm called **{keycloak-realm}** +2. Create a client called **{simple-microservice-client-secured}**; in the Capability config, turn on +++Client authentication+++. +3. For the **{simple-microservice-client-secured}** client, we also need to set the valid redirect URIs to ***** and set the Web origins to **+** to permit all origins of Valid Redirect URIs. +4. For the **{simple-microservice-client-secured}** client, note down the +++Client Secret+++ in the +++Credentials+++ tab (e.g. `KqIQIzNHD9LnCRjsCxblDnfEl4rcNoKB`); +5. Now, click on Realm roles and create two roles, **{keycloak-role1}** and **{keycloak-role2}**. +6. Create a user called **{keycloak-user1}** and assign her the **{keycloak-role1}** and **{keycloak-role2}** roles; set password **{keycloak-user1-pws}** for **{keycloak-user1}** +7. Create a user called **{keycloak-user2}** and assign him only the **{keycloak-role1}** role; set password **{keycloak-user2-pws}** for **{keycloak-user2}** + +NOTE: in case you want to go deeper, find more information and examples in link:https://wildfly-security.github.io/wildfly-elytron/blog/bearer-only-support-openid-connect/[Setting up your Keycloak OpenID provider] + +== Web Application (Microservice A) + +=== Maven Project + +Copy link:{source-code-git-repository}/simple-microservice-rest-client/simple-microservice-client[simple-microservice-client] +into a new folder named **{simple-microservice-client-secured}**; + +==== pom.xml + +Set `{simple-microservice-client-secured}`; + +Add the following to `dependencies`: + +[source,xml,subs="normal"] +---- + + org.wildfly.security + wildfly-elytron-http-oidc + provided + + + jakarta.servlet + jakarta.servlet-api + provided + +---- + +Add the following `layers` in the `wildfly-maven-plugin`: + +[source,xml,subs="normal"] +---- + elytron-oidc-client +---- + +==== web.xml + +Create file `src/main/webapp/WEB-INF/web.xml` with the following content: + +.src/main/webapp/WEB-INF/web.xml: +[source,xml,subs="normal"] +---- + + + + + + secured + /* + + + {keycloak-role1} + {keycloak-role2} + + + + + OIDC + + + + {keycloak-role1} + + + {keycloak-role2} + + +---- + +==== oidc.json + +Create file `src/main/webapp/WEB-INF/oidc.json` with the following content: + +.src/main/webapp/WEB-INF/oidc.json: +[source,json,subs="normal"] +---- +{ + "client-id" : "{simple-microservice-client-secured}", + "provider-url" : "${env.OIDC_PROVIDER_URL:http://localhost:8080}/realms/{keycloak-realm}", + "ssl-required" : "EXTERNAL", + "credentials" : { + "secret" : "${env.OIDC_CLIENT_SECRET:KqIQIzNHD9LnCRjsCxblDnfEl4rcNoKB}" + } +} +---- + +replace `KqIQIzNHD9LnCRjsCxblDnfEl4rcNoKB` with the +++Client Secret+++ you previously noted down; + +==== Java code + +===== GettingStartedEndpointInterface + +Add the following interface: + +.org.wildfly.examples.GettingStartedEndpointInterface.java: +[source,java] +---- +package org.wildfly.examples; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterClientHeaders +@RegisterRestClient(configKey="simple-microservice-server") +@Path("/hello") +public interface GettingStartedEndpointInterface { + @GET + @Path("/{name}") + @Produces(MediaType.TEXT_PLAIN) + public Response sayHello(@HeaderParam("Authorization") String authorization, final @PathParam("name") String name); +} +---- + +===== GettingStartedEndpoint + +Modify the following class: + +.org.wildfly.examples.GettingStartedEndpoint.java: +[source,java] +---- +package org.wildfly.examples; + +import jakarta.inject.Inject; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.wildfly.security.http.oidc.OidcSecurityContext; + +import java.io.IOException; + +@Path("/") +public class GettingStartedEndpoint { + + @Context + private HttpServletRequest httpServletRequest; + + @Inject + @RestClient + private GettingStartedEndpointInterface service; + + @GET + @Path("/{name}") + @Produces(MediaType.TEXT_PLAIN) + public Response sayHello(final @PathParam("name") String name) throws IOException { + Response response; + OidcSecurityContext oidcSecurityContext = getOidcSecurityContext(httpServletRequest); + if (oidcSecurityContext != null) { + String authzHeaderValue = "Bearer " + oidcSecurityContext.getTokenString(); + System.out.println("\n\n[JWT] service Token: " + authzHeaderValue + "\n\n"); + return service.sayHello(authzHeaderValue, name); + } else { + System.out.println("\n\n[JWT] No token :(\n\n"); + return service.sayHello(null, name); + } + } + + private OidcSecurityContext getOidcSecurityContext(HttpServletRequest req) { + return (OidcSecurityContext) req.getAttribute(OidcSecurityContext.class.getName()); + } +} +---- + +=== Build and push the image to Quay.io + +Build the application: + +[source,bash] +---- +mvn clean package +---- + +Build the Docker image: + +[source,bash,subs="normal"] +---- +podman build -t {simple-microservice-client-secured}:latest . +---- + +Create a public repository named `{simple-microservice-client-secured}` on link:https://quay.io[quay.io, window="_blank"] (e.g. link:https://quay.io/repository/{quay-io-account-name}/{simple-microservice-client-secured}[https://quay.io/repository/{quay-io-account-name}/{simple-microservice-client-secured}, window="_blank"]). + +NOTE: replace `{quay-io-account-name}` with the name of your account in all the commands that will follow + +Tag the Docker image: + +[source,bash,subs="normal"] +---- +podman tag {simple-microservice-client-secured} quay.io/{quay-io-account-name}/{simple-microservice-client-secured} +---- + +Push the `{simple-microservice-client-secured}` Docker Image: + +[source,bash,subs="normal"] +---- +podman push quay.io/{quay-io-account-name}/{simple-microservice-client-secured} +---- + +=== Deploy to Kubernetes + +Create file `{simple-microservice-client-secured}-deployment.yaml`: + +.{simple-microservice-client-secured}-deployment.yaml: +[source,yaml,subs="normal"] +---- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {simple-microservice-client-secured}-deployment + labels: + app: {simple-microservice-client-secured} +spec: + replicas: 1 + selector: + matchLabels: + app: {simple-microservice-client-secured} + template: + metadata: + labels: + app: {simple-microservice-client-secured} + spec: + containers: + - name: {simple-microservice-client-secured} + image: quay.io/{quay-io-account-name}/{simple-microservice-client-secured} + ports: + - containerPort: 8080 + - containerPort: 9990 + livenessProbe: + httpGet: + path: /health/live + port: 9990 + readinessProbe: + httpGet: + path: /health/ready + port: 9990 + startupProbe: + httpGet: + path: /health/started + port: 9990 + env: + - name: SIMPLE-MICROSERVICE-SERVER_MP_REST_URI + value: "http://{simple-microservice-server-secured}-service:8080" + - name: OIDC_PROVIDER_URL + # replace with the outcome of "minikube service keycloak-external --url" + value: "http://192.168.39.190:31950" + - name: OIDC_CLIENT_SECRET + value: "KqIQIzNHD9LnCRjsCxblDnfEl4rcNoKB" +---- + +Then: + +* replace "http://192.168.39.190:31950" with the outcome of command `minikube service keycloak-external --url` +* replace "{quay-io-account-name}" with your account name on link:quay.io[quay.io] +* replace `KqIQIzNHD9LnCRjsCxblDnfEl4rcNoKB` with the +++Client Secret+++ you previously noted down; + +Deploy to your Kubernetes Cluster: + +[source,bash,subs="normal"] +---- +kubectl apply -f {simple-microservice-client-secured}-deployment.yaml +---- + +Create file `{simple-microservice-client-secured}-service.yaml`: + +.{simple-microservice-client-secured}-service.yaml: +[source,yaml,subs="normal"] +---- +apiVersion: v1 +kind: Service +metadata: + name: {simple-microservice-client-secured}-service + labels: + app: {simple-microservice-client-secured} +spec: + ports: + - name: http + protocol: TCP + port: 8080 + targetPort: 8080 + selector: + app: {simple-microservice-client-secured} + type: NodePort +---- + +Deploy to your Kubernetes Cluster: + +[source,bash,subs="normal"] +---- +kubectl apply -f {simple-microservice-client-secured}-service.yaml +---- + +== Service (Microservice B) + +=== Maven Project + +Copy link:{source-code-git-repository}/simple-microservice-rest-client/simple-microservice-server[simple-microservice-server] +into a new folder named **simple-microservice-server-secured**; + +==== pom.xml + +Set `simple-microservice-server-secured`; + +Add the following to `dependencyManagement`: + +[source,xml,subs="normal"] +---- + + org.wildfly.bom + wildfly-microprofile + ${version.wildfly.bom} + pom + import + +---- + +Add the following to `dependencies`: + +[source,xml,subs="normal"] +---- + + org.eclipse.microprofile.config + microprofile-config-api + provided + + + org.eclipse.microprofile.jwt + microprofile-jwt-auth-api + provided + +---- + +Add the following `layers` in the `wildfly-maven-plugin`: + +[source,xml,subs="normal"] +---- + microprofile-config + microprofile-jwt +---- + +=== microprofile-config.properties + +Add file `src/main/resources/META-INF/microprofile-config.properties` with the following content: + +.microprofile-config.properties: +[source,properties] +---- +mp.jwt.verify.publickey.location=http://localhost:8080/realms/keycloak-realm/protocol/openid-connect/certs +---- + +=== Java code + +==== GettingStartedApplication + +Modify the following class: + +.org.wildfly.examples.GettingStartedApplication.java: +[source,java] +---- +package org.wildfly.examples; + +import jakarta.ws.rs.ApplicationPath; +import jakarta.ws.rs.core.Application; +import org.eclipse.microprofile.auth.LoginConfig; + +@LoginConfig(authMethod="MP-JWT") +@ApplicationPath("/hello") +public class GettingStartedApplication extends Application { + +} +---- + +==== GettingStartedEndpoint + +Modify the following class: + +.org.wildfly.examples.GettingStartedEndpoint.java: +[source,java] +---- +package org.wildfly.examples; + +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.jwt.JsonWebToken; + +@Path("/") +public class GettingStartedEndpoint { + + @Inject + @ConfigProperty(name = "mp.jwt.verify.publickey.location") + private String publicKeyLocation; + + @Inject + JsonWebToken jwt; + + @GET + @Path("/{name}") + @Produces(MediaType.TEXT_PLAIN) + @RolesAllowed({"admin"}) + public Response sayHello(final @PathParam("name") String name) { + System.out.println("mp.jwt.verify.publickey.location=" + publicKeyLocation); + + String response = + "Hello " + name + + (jwt != null ? (" Subject:" + jwt.getSubject()) : null) + + (jwt != null ? (" Issuer: " + jwt.getIssuer()) : null); + + return Response.ok(response).build(); + } +} +---- + +=== Build and push the image to Quay.io + +Create a public repository named `{simple-microservice-server-secured}` on link:https://quay.io[quay.io, window="_blank"] (e.g. link:https://quay.io/repository/{quay-io-account-name}/my-jaxrs-app[https://quay.io/repository/{quay-io-account-name}/{simple-microservice-server-secured}, window="_blank"]). + +NOTE: replace `{quay-io-account-name}` with the name of your account in all the commands that will follow + +Tag the Docker image: + +[source,bash,subs="normal"] +---- +podman tag {simple-microservice-server-secured} quay.io/{quay-io-account-name}/{simple-microservice-server-secured} +---- + +Push the `{simple-microservice-server-secured}` Docker Image: + +[source,bash,subs="normal"] +---- +podman push quay.io/{quay-io-account-name}/{simple-microservice-server-secured} +---- + +=== Deploy to Kubernetes + +Create file `{simple-microservice-server-secured}-deployment.yaml`: + +.{simple-microservice-server-secured}-deployment.yaml: +[source,yaml,subs="normal"] +---- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {simple-microservice-server-secured}-deployment + labels: + app: {simple-microservice-server-secured} +spec: + replicas: 1 + selector: + matchLabels: + app: {simple-microservice-server-secured} + template: + metadata: + labels: + app: {simple-microservice-server-secured} + spec: + containers: + - name: {simple-microservice-server-secured} + image: quay.io/tborgato/{simple-microservice-server-secured} + ports: + - containerPort: 8080 + - containerPort: 9990 + livenessProbe: + httpGet: + path: /health/live + port: 9990 + readinessProbe: + httpGet: + path: /health/ready + port: 9990 + startupProbe: + httpGet: + path: /health/started + port: 9990 + env: + - name: MP_JWT_VERIFY_PUBLICKEY_LOCATION + value: "http://keycloak-internal:8080/realms/{keycloak-realm}/protocol/openid-connect/certs" +---- + +Then: + +* replace "{quay-io-account-name}" with your account name on link:quay.io[quay.io] + +Deploy to your Kubernetes Cluster: + +[source,bash,subs="normal"] +---- +kubectl apply -f {simple-microservice-server-secured}-deployment.yaml +---- + +Create file `{simple-microservice-server-secured}-service.yaml`: + +.{simple-microservice-server-secured}-service.yaml: +[source,yaml,subs="normal"] +---- +apiVersion: v1 +kind: Service +metadata: + name: {simple-microservice-server-secured}-service + labels: + app: {simple-microservice-server-secured} +spec: + ports: + - name: http + protocol: TCP + port: 8080 + targetPort: 8080 + selector: + app: {simple-microservice-server-secured} + type: ClusterIP +---- + +Deploy to your Kubernetes Cluster: + +[source,bash,subs="normal"] +---- +kubectl apply -f {simple-microservice-server-secured}-service.yaml +---- + +== Test + +[source,bash,subs="normal"] +---- +$ minikube service {simple-microservice-client-secured}-service --url +http://192.168.39.190:32225 +---- + +Open that URL in your browser, log in as *{keycloak-user1}*/*{keycloak-user1-pws}* and try it out! + +After pressing the "Say Hello" button, you should see something like: + +[source,text] +---- +Hello ddd Subject:aaef43ee-4005-4d2d-a5f0-0e0d11a1f831 Issuer: http://192.168.39.190:31950/realms/keycloak-realm +---- + + +[[references]] +== References + +* https://wildfly-security.github.io/wildfly-elytron/blog/bearer-only-support-openid-connect/ +* https://github.com/wildfly-security-incubator/elytron-examples/tree/main/oidc-with-bearer +* https://www.keycloak.org/getting-started/getting-started-kube +* Source code for this guide: +** {source-code-git-repository}/simple-microservice-rest-client/simple-microservice-client-secured +** {source-code-git-repository}/simple-microservice-rest-client/simple-microservice-server-secured \ No newline at end of file