์๋
ํ์ธ์ ์ฌ๋ฌ๋ถ! ์ฌ๊ธฐ ์์ต๋๋ค - Quarkus ์๋ฆฌ์ฆ์ ๋ง์ง๋ง ๊ฒ์๋ฌผ์
๋๋ค! (๊ทธ๋์ ๋ ์น ์ธ๋ฏธ๋๋ฅผ ์์ฒญํด ๋ณด์ธ์.
ะ
๋ฒ์ 0.17.0๋ถํฐ,
OpenShift ํ๋ซํผ์ AMQ Online์ ์ด๋ฏธ ๋ฐฐํฌํ๋ค๊ณ ๊ฐ์ ํฉ๋๋ค(๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ ๋ค์์ ์ฐธ์กฐํ์ธ์).
๋จผ์ ๋ฐ์ํ ๋ฉ์์ง์ ์ฌ์ฉํ๋ ๊ฐ๋จํ ์ฃผ๋ฌธ ์ฒ๋ฆฌ ์์คํ ์ด ๋ Quarkus ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ญ๋๋ค. ์ด ์ ํ๋ฆฌ์ผ์ด์ ์๋ ๊ณ ์ ๋ ๊ฐ๊ฒฉ์ผ๋ก ๋ฉ์์ง ๋๊ธฐ์ด์ ์ฃผ๋ฌธ์ ๋ณด๋ด๋ ์ฃผ๋ฌธ ์์ฑ๊ธฐ์ ๋๊ธฐ์ด์ ๋ฉ์์ง๋ฅผ ์ฒ๋ฆฌํ๊ณ ๋ธ๋ผ์ฐ์ ์์ ๋ณผ ์ ์๋ ํ์ธ ๋ฉ์์ง๋ฅผ ์์ฑํ๋ ์ฃผ๋ฌธ ํ๋ก์ธ์๊ฐ ํฌํจ๋ฉ๋๋ค.
์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ ํ์๋ ๋ฉ์์ง ์์คํ ๊ตฌ์ฑ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ํฌํจํ๊ณ AMQ Online์ ์ฌ์ฉํ์ฌ ์์คํ ์ ํ์ํ ๋ฆฌ์์ค๋ฅผ ํ๋ก๋น์ ๋ํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ ๋๋ฆฌ๊ฒ ์ต๋๋ค.
Quarkus ์ฑ
Quarkus ์ ํ๋ฆฌ์ผ์ด์
์ OpenShift์์ ์คํ๋๋ฉฐ ํ๋ก๊ทธ๋จ์ ์์ ๋ ๋ฒ์ ์
๋๋ค.
์ฃผ๋ฌธ ์์ฑ๊ธฐ
์์ฑ๊ธฐ๋ 5์ด๋ง๋ค ์ฆ๊ฐํ๋ ์ฃผ๋ฌธ ID๋ฅผ "์ฃผ๋ฌธ" ์ฃผ์๋ก ๋จ์กฐ๋กญ๊ฒ ๋ณด๋ ๋๋ค.
@ApplicationScoped
public class OrderGenerator {
private int orderId = 1;
@Outgoing("orders")
public Flowable<Integer> generate() {
return Flowable.interval(5, TimeUnit.SECONDS)
.map(tick -> orderId++);
}
}
์ฃผ๋ฌธ ์ฒ๋ฆฌ์
์ฃผ๋ฌธ ์ฒ๋ฆฌ๊ธฐ๋ ํจ์ฌ ๋ ๊ฐ๋จํฉ๋๋ค. ํ์ธ ID๋ฅผ "ํ์ธ" ์ฃผ์๋ก ๋ฐํํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
@ApplicationScoped
public class OrderProcessor {
@Incoming("orders")
@Outgoing("confirmations")
public Integer process(Integer order) {
// ะะดะตะฝัะธัะธะบะฐัะพั ะฟะพะดัะฒะตัะถะดะตะฝะธั ัะฐะฒะตะฝ ัะดะฒะพะตะฝะฝะพะผั ะธะดะตะฝัะธัะธะบะฐัะพัั ะทะฐะบะฐะทะฐ <img draggable="false" class="emoji" alt=":-)" src="https://s.w.org/images/core/emoji/11.2.0/svg/1f642.svg">
return order * 2;
}
}
ํ์ธ ์๋ฃ
ํ์ธ ๋ฆฌ์์ค๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์์ฑ๋ ํ์ธ์ ๋์ดํ๊ธฐ ์ํ HTTP ์๋ํฌ์ธํธ์ ๋๋ค.
@Path("/confirmations")
public class ConfirmationResource {
@Inject
@Stream("confirmations") Publisher<Integer> orders;
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}
@GET
@Path("/stream")
@Produces(MediaType.SERVER_SENT_EVENTS)
public Publisher<Integer> stream() {
return orders;
}
}
์กฐ์
AMQ Online์ ์ฐ๊ฒฐํ๋ ค๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ Quarkus ์ปค๋ฅํฐ ๊ตฌ์ฑ, AMQP ์๋ํฌ์ธํธ ์ ๋ณด ๋ฐ ํด๋ผ์ด์ธํธ ์๊ฒฉ ์ฆ๋ช ๊ณผ ๊ฐ์ ์ผ๋ถ ๊ตฌ์ฑ ๋ฐ์ดํฐ๊ฐ ํ์ํฉ๋๋ค. ๋ฌผ๋ก ๋ชจ๋ ๊ตฌ์ฑ ๋ฐ์ดํฐ๋ฅผ ํ ๊ณณ์ ๋ณด๊ดํ๋ ๊ฒ์ด ๋ ์ข์ง๋ง Quarkus ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ์ฑ์ ๊ฐ๋ฅํ ์ต์ ์ ๋ณด์ฌ์ฃผ๊ธฐ ์ํด ์๋์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฆฌํ ๊ฒ์ ๋๋ค.
์ปค๋ฅํฐ
์ ํ๋ฆฌ์ผ์ด์ ์์ฑ ํ์ผ์ ์ฌ์ฉํ์ฌ ์ปดํ์ผ ํ์์ ์ปค๋ฅํฐ ๊ตฌ์ฑ์ ์ ๊ณตํ ์ ์์ต๋๋ค.
mp.messaging.outgoing.orders.connector=smallrye-amqp
mp.messaging.incoming.orders.connector=smallrye-amqp
์ผ์ ๋จ์ํ๊ฒ ์ ์งํ๊ธฐ ์ํด "์ฃผ๋ฌธ" ์ฃผ์์ ๋ํด์๋ง ๋ฉ์์ง ๋๊ธฐ์ด์ ์ฌ์ฉํ๊ฒ ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฐ๋ฆฌ ์ ํ๋ฆฌ์ผ์ด์ ์ "ํ์ธ" ์ฃผ์๋ ๋ฉ๋ชจ๋ฆฌ์ ๋๊ธฐ์ด์ ์ฌ์ฉํฉ๋๋ค.
AMQP ์๋ํฌ์ธํธ
์ปดํ์ผ ํ์์๋ AMQP ์๋ํฌ์ธํธ์ ํธ์คํธ ์ด๋ฆ๊ณผ ํฌํธ ๋ฒํธ๋ฅผ ์ ์ ์์ผ๋ฏ๋ก ์ฝ์ ํด์ผ ํฉ๋๋ค. ์๋ํฌ์ธํธ๋ AMQ Online์์ ์์ฑ๋ configmap์์ ์ค์ ํ ์ ์์ผ๋ฏ๋ก ์ ํ๋ฆฌ์ผ์ด์ ๋งค๋ํ์คํธ์ ํ๊ฒฝ ๋ณ์๋ฅผ ํตํด ์๋ํฌ์ธํธ๋ฅผ ์ ์ํฉ๋๋ค.
spec:
template:
spec:
containers:
- env:
- name: AMQP_HOST
valueFrom:
configMapKeyRef:
name: quarkus-config
key: service.host
- name: AMQP_PORT
valueFrom:
configMapKeyRef:
name: quarkus-config
key: service.port.amqp
์ ์์ฅ
์๋น์ค ๊ณ์ ํ ํฐ์ OpenShift์ ๋ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ธ์ฆํ๋ ๋ฐ ์ฌ์ฉ๋ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ ค๋ฉด ๋จผ์ ํฌ๋์ ํ์ผ ์์คํ ์์ ์ธ์ฆ ํ ํฐ์ ์ฝ๋ ์ฌ์ฉ์ ์ง์ ConfigSource๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.
public class MessagingCredentialsConfigSource implements ConfigSource {
private static final Set<String> propertyNames;
static {
propertyNames = new HashSet<>();
propertyNames.add("amqp-username");
propertyNames.add("amqp-password");
}
@Override
public Set<String> getPropertyNames() {
return propertyNames;
}
@Override
public Map<String, String> getProperties() {
try {
Map<String, String> properties = new HashMap<>();
properties.put("amqp-username", "@@serviceaccount@@");
properties.put("amqp-password", readTokenFromFile());
return properties;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public String getValue(String key) {
if ("amqp-username".equals(key)) {
return "@@serviceaccount@@";
}
if ("amqp-password".equals(key)) {
try {
return readTokenFromFile();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
return null;
}
@Override
public String getName() {
return "messaging-credentials-config";
}
private static String readTokenFromFile() throws IOException {
return new String(Files.readAllBytes(Paths.get("/var/run/secrets/kubernetes.io/serviceaccount/token")), StandardCharsets.UTF_8);
}
}
์ ํ๋ฆฌ์ผ์ด์ ๋น๋ ๋ฐ ๋ฐฐํฌ
์ ํ๋ฆฌ์ผ์ด์
์ ์คํ ํ์ผ๋ก ์ปดํ์ผํด์ผ ํ๋ฏ๋ก GraalVM ๊ฐ์ ๋จธ์ ์ด ํ์ํฉ๋๋ค. ์ด์ ๋ํ ํ๊ฒฝ์ ์ค์ ํ๋ ๋ฐฉ๋ฒ์ ๋ํ ์์ธํ ๋ด์ฉ์ ํด๋น ์ง์นจ์ ์ฐธ์กฐํ์ธ์.
๊ทธ๋ฐ ๋ค์ ๊ฑฐ๊ธฐ์ ์ ๊ณต๋ ์ง์นจ์ ๋ฐ๋ผ ์์ค๋ฅผ ๋ค์ด๋ก๋ํ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋น๋ ๋ฐ ๋ฐฐํฌํด์ผ ํฉ๋๋ค.
git clone https://github.com/EnMasseProject/enmasse-example-clients
cd enmasse-example-clients/quarkus-example-client
oc new-project myapp
mvn -Pnative -Dfabric8.mode=openshift -Dfabric8.build.strategy=docker package fabric8:build fabric8:resource fabric8:apply
์ด๋ฌํ ๋ช ๋ น ํ์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋ฐฐํฌ๋์ง๋ง AMQ Online์ ํ์ํ ๋ฉ์์ง ๋ฆฌ์์ค๋ฅผ ๊ตฌ์ฑํ ๋๊น์ง๋ ์์๋์ง ์์ต๋๋ค.
๋ฉ์์ง ์์คํ ์ค์
์ด์ ๋จ์ ๊ฒ์ ๋ฉ์์ง ์์คํ ์์ ์ ํ๋ฆฌ์ผ์ด์ ์ ํ์ํ ๋ฆฌ์์ค๋ฅผ ์ค์ ํ๋ ๊ฒ์ ๋๋ค. ์ด๋ ๊ฒ ํ๋ ค๋ฉด ๋ค์์ ๋ง๋ค์ด์ผ ํฉ๋๋ค. 1) ๋ฉ์์ง ์์คํ ๋์ ์ ์ด๊ธฐํํ๊ธฐ ์ํ ์ฃผ์ ๊ณต๊ฐ 2) ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ฌ์ฉํ๋ ์ฃผ์๋ฅผ ๊ตฌ์ฑํ๊ธฐ ์ํ ์ฃผ์ 3) ์ฌ์ฉ์์๊ฒ ํด๋ผ์ด์ธํธ ์๊ฒฉ ์ฆ๋ช ์ ์ค์ ํ๋ผ๋ ๋ฉ์์ง๋ฅผ ๋ณด๋ ๋๋ค.
์ฃผ์ ๊ณต๊ฐ
AMQ Online์ AddressSpace ๊ฐ์ฒด๋ ์ฐ๊ฒฐ ๋์ ๊ณผ ์ธ์ฆ ๋ฐ ๊ถํ ๋ถ์ฌ ์ ์ฑ ์ ๊ณต์ ํ๋ ์ฃผ์ ๊ทธ๋ฃน์ ๋๋ค. ์ฃผ์ ๊ณต๊ฐ์ ๋ง๋ค ๋ ๋ฉ์์ง ๋์ ์ด ๋ ธ์ถ๋๋ ๋ฐฉ๋ฒ์ ์ง์ ํ ์ ์์ต๋๋ค.
apiVersion: enmasse.io/v1beta1
kind: AddressSpace
metadata:
name: quarkus-example
spec:
type: brokered
plan: brokered-single-broker
endpoints:
- name: messaging
service: messaging
exports:
- name: quarkus-config
kind: configmap
์ฃผ์
์ฃผ์๋ ๋ฉ์์ง๋ฅผ ๋ณด๋ด๊ณ ๋ฐ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. ๊ฐ ์ฃผ์์๋ ์๋ฏธ๋ฅผ ๊ฒฐ์ ํ๋ ์ ํ๊ณผ ์์ฝํ ๋ฆฌ์์ค ์๋ฅผ ์ง์ ํ๋ ๊ณํ์ด ์์ต๋๋ค. ์ฃผ์๋ ์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ด ํ์ธํ ์ ์์ต๋๋ค.
apiVersion: enmasse.io/v1beta1
kind: Address
metadata:
name: quarkus-example.orders
spec:
address: orders
type: queue
plan: brokered-queue
๋ฉ์์ง ์ฌ์ฉ์
์ ๋ขฐํ ์ ์๋ ์์ฉ ํ๋ก๊ทธ๋จ๋ง ํด๋น ์ฃผ์๋ก ๋ฉ์์ง๋ฅผ ๋ณด๋ด๊ณ ๋ฐ์ ์ ์๋๋ก ํ๋ ค๋ฉด ๋ฉ์์ง ์์คํ ์์ ์ฌ์ฉ์๋ฅผ ๋ง๋ค์ด์ผ ํฉ๋๋ค. ํด๋ฌ์คํฐ์์ ์คํ๋๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฒฝ์ฐ OpenShift ์๋น์ค ๊ณ์ ์ ์ฌ์ฉํ์ฌ ํด๋ผ์ด์ธํธ๋ฅผ ์ธ์ฆํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, "serviceaccount" ์ฌ์ฉ์๋ ๋ค์๊ณผ ๊ฐ์ด ์ ์ํ ์ ์์ต๋๋ค.
apiVersion: user.enmasse.io/v1beta1
kind: MessagingUser
metadata:
name: quarkus-example.app
spec:
username: system:serviceaccount:myapp:default
authentication:
type: serviceaccount
authorization:
- operations: ["send", "recv"]
addresses: ["orders"]
์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ฑํ ์ ์๋ ๊ถํ
AMQ Online์์ AMQP ์๋ํฌ์ธํธ ์ ๋ณด๋ฅผ ํฌํจํ๋ ๋ฐ ์ฌ์ฉํ configmap์ ์์ฑํ๋ ค๋ฉด Role ๋ฐ RoleBinding์ ์ค์ ํด์ผ ํฉ๋๋ค.
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: quarkus-config
spec:
rules:
- apiGroups: [ "" ]
resources: [ "configmaps" ]
verbs: [ "create" ]
- apiGroups: [ "" ]
resources: [ "configmaps" ]
resourceNames: [ "quarkus-config" ]
verbs: [ "get", "update", "patch" ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: quarkus-config
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: quarkus-config
subjects:
- kind: ServiceAccount
name: address-space-controller
namespace: amq-online-infra
๊ตฌ์ฑ์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ
๋ค์๊ณผ ๊ฐ์ด ๋ฉ์์ง ์์คํ ๊ตฌ์ฑ์ ์ ์ฉํ ์ ์์ต๋๋ค.
cd enmasse-example-clients/quarkus-example-client
oc project myapp
oc apply -f src/main/resources/k8s/addressspace
oc apply -f src/main/resources/k8s/address
์ ์ฒญ์ ํ์ธ
์ ํ๋ฆฌ์ผ์ด์ ์ด ์์๋์๋์ง ํ์ธํ๊ธฐ ์ํด ๋จผ์ ํด๋น ์ฃผ์๊ฐ ์์ฑ๋์๊ณ ํ์ฑํ๋์๋์ง ํ์ธํ๊ฒ ์ต๋๋ค.
until [[ `oc get address quarkus-example.prices -o jsonpath='{.status.phase}'` == "Active" ]]; do echo "Not yet ready"; sleep 5; done
๊ทธ๋ฐ ๋ค์ ์ ํ๋ฆฌ์ผ์ด์ ๊ฒฝ๋ก URL์ ํ์ธํด ๋ณด๊ฒ ์ต๋๋ค(๋ธ๋ผ์ฐ์ ์์ ์ด ์ฃผ์๋ฅผ ์ด๋ฉด ๋ฉ๋๋ค).
echo "http://$(oc get route quarkus-example-client -o jsonpath='{.spec.host}')/prices.html"
๋ธ๋ผ์ฐ์ ๋ AMQ Online์์ ๋ฉ์์ง๋ฅผ ๋ณด๋ด๊ณ ๋ฐ์ ๋ ํฐ์ผ์ด ์ฃผ๊ธฐ์ ์ผ๋ก ์ ๋ฐ์ดํธ๋๋ค๋ ๊ฒ์ ํ์ํด์ผ ํฉ๋๋ค.
ํฉ์ฐ
๊ทธ๋์ ์ฐ๋ฆฌ๋ ๋ฉ์์ง์ AMQP๋ฅผ ์ฌ์ฉํ๋ Quarkus ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ฑํ๊ณ , Red Hat OpenShift ํ๋ซํผ์์ ์คํ๋๋๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ฑํ๊ณ , AMQ Online ๊ตฌ์ฑ์ ๊ธฐ๋ฐ์ผ๋ก ํด๋น ๊ตฌ์ฑ์ ๊ตฌํํ์ต๋๋ค. ๊ทธ๋ฐ ๋ค์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฉ์์ง ์์คํ ์ ์ด๊ธฐํํ๋ ๋ฐ ํ์ํ ๋งค๋ํ์คํธ๋ฅผ ๋ง๋ค์์ต๋๋ค.
์ด๊ฒ์ผ๋ก Quarkus์ ๋ํ ์๋ฆฌ์ฆ๊ฐ ๋๋ฌ์ต๋๋ค. ์์ผ๋ก ์๋กญ๊ณ ํฅ๋ฏธ๋ก์ด ์ผ์ด ๋ง์ด ์์ ๊ฒ์ด๋ฏ๋ก ๊ณ์ ์ง์ผ๋ด ์ฃผ์๊ธฐ ๋ฐ๋๋๋ค!
์ถ์ฒ : habr.com