tag:blogger.com,1999:blog-87279654055391530822024-03-19T00:31:38.249-07:00ADF PracticeEugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.comBlogger148125tag:blogger.com,1999:blog-8727965405539153082.post-47903273039933885102019-05-01T11:55:00.000-07:002019-06-09T13:56:33.252-07:00Running PostgreSQL in a Cloud on Oracle Containers Engine for Kubernetes<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
In this post I am going to show a few steps to deploy and run <b><a href="https://www.postgresql.org/">PostgreSQL</a></b> database in a K8S cluster on <a href="https://docs.cloud.oracle.com/iaas/Content/ContEng/Concepts/contengoverview.htm" style="font-weight: bold;">OKE</a>.<br />
<b><br /></b>
The deployment is going to be based on <a href="https://hub.docker.com/_/postgres">postgres:11.1</a> Docker image which requires a few environment variables to be configured: POSTGRES_DB (database name), POSTGRES_USER and POSTGRES_PASSWORD. I am going to store values of these variables in K8S <b>ConfigMap </b>and <b>Secret:</b><br />
<pre class="java" name="code">apiVersion: v1
kind: ConfigMap
metadata:
name: postgre-db-config
data:
db-name: flexdeploy
</pre>
<div>
<br />
<pre class="java" name="code">apiVersion: v1
kind: Secret
metadata:
name: postgre-db-secret
stringData:
username: creator
password: c67</pre>
</div>
<br />
These configuration K8S resources are referenced by the <b>StatefulSet </b>which actually takes care of the lifespan of a pod with <b>postgres-db </b>container:<br />
<pre class="java" name="code">apiVersion: apps/v1beta2
kind: StatefulSet
metadata:
name: postgre-db
labels:
run: postgre-db
spec:
selector:
matchLabels:
run: postgre-db
serviceName: "postgre-db-svc"
replicas: 1
template:
metadata:
labels:
run: postgre-db
spec:
containers:
- image: postgres:11.1
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: db
env:
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: postgre-db-config
key: db-name
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgre-db-secret
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgre-db-secret
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
name: postgre-db
ports:
- containerPort: 5432
volumeClaimTemplates:
- metadata:
name: db
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 3Gi </pre>
<br />
<br />
<b>StatefulSet</b> K8S resource has been specially designed for stateful applications like database that save their data to a persistent storage. In order to define a persistent storage for our database we use another K8s resource <b>Persistent Volume</b> and here in the manifest file we are defining a claim to create a 3Gi <b>Persistent Volume</b> with name <b>db</b>. The volume is called persistent because its lifespan is not maintained by a container and not even by a pod, it’s maintained by a K8s cluster. So it can <b>outlive any containers and pods</b> and save the data. Meaning that if we kill or recreate a container or a pod or even the entire StatefulSet, the data will be still there. We are referring to this persistence volume in the container definition mounting a volume on path <b>/var/lib/postgresql/data</b>. This is where PostgreSQL container stores its data.<br />
<br />
In order to access the database we are going to create a <b>service</b>:<br />
<pre class="java" name="code">apiVersion: v1
kind: Service
metadata:
name: postgre-db-svc
spec:
selector:
run: postgre-db
ports:
- port: 5432
targetPort: 5432
type: LoadBalancer
</pre>
<div>
This is a <b>LoadBalancer</b> service which is accessible from outside of the K8S cluster:<br />
<pre class="java" name="code">$ kubectl get svc postgre-db-svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
postgre-db-svc LoadBalancer 10.96.177.34 129.146.211.77 5432:32498/TCP 39m</pre>
</div>
</div>
<div>
<br /></div>
Assuming that we have created a K8s cluster on <b>OKE</b> and our <b>kubectl</b> is configured to communicate with it we can apply the manifest files to the cluster:<br />
<pre class="java" name="code">kubectl apply -f postgre-db-config.yaml
kubectl apply -f postgre-db-secret.yaml
kubectl apply -f postgre-db-pv.yaml
kubectl apply -f postgre-db-service.yaml
</pre>
Having done that, we can connect to the database from our favorite IDE on our laptop<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-6AOmMHPQ5hk/XMXxEAdDLFI/AAAAAAAACHY/FoYBGISHKycSZdPRAWLYD51vSzvTFLWngCLcBGAs/s1600/Screen%2BShot%2B2019-04-28%2Bat%2B1.29.14%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1134" data-original-width="1228" height="295" src="https://2.bp.blogspot.com/-6AOmMHPQ5hk/XMXxEAdDLFI/AAAAAAAACHY/FoYBGISHKycSZdPRAWLYD51vSzvTFLWngCLcBGAs/s320/Screen%2BShot%2B2019-04-28%2Bat%2B1.29.14%2BPM.png" width="320" /></a></div>
The manifest files for this post are available on <a href="https://github.com/eedorenko/PostgreSQLOnK8s">GitHub</a>.<br />
<br />
That's it!</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-20010260317921290032019-04-20T12:14:00.001-07:002019-04-20T12:22:22.874-07:00Deployment Strategies with Kubernetes and Istio<div dir="ltr" style="text-align: left;" trbidi="on">
In this post I am going to discuss various deployment strategies and how they can be implemented with K8s and Istio. Basically the implementation of all strategies is based on the ability of K8s to run multiple versions of a microservice simultaneously and on the concept that consumers can access the microservice only through some entry point. At that entry point we can control what version of a microservice the consumer should be routed to.<br />
<br />
The sample application for this post is going to be a simple Spring Boot application wrapped into a Docker image. So there are two images <b>superapp:old</b> and <b>superapp:new </b>representing an old and a new versions of the application respectively:<br />
<br />
<div style="font-family: Menlo; font-size: 14px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">docker run -d --name old -p 9001:8080 eugeneflexagon/superapp:old</span></div>
<div style="font-family: Menlo; font-size: 14px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"></span></div>
<div style="font-family: Menlo; font-size: 14px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">docker run -d --name new -p 9002:8080 eugeneflexagon/superapp:new</span></div>
<div style="font-family: Menlo; font-size: 14px; font-stretch: normal; line-height: normal;">
<br /></div>
<br />
<div style="font-family: Menlo; font-size: 14px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">curl http://localhost:9001/version</span></div>
<div style="font-family: Menlo; font-size: 14px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"></span></div>
<div style="font-family: Menlo; font-size: 14px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">{"id":1,"content":"old"}</span></div>
<div style="font-family: Menlo; font-size: 14px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="font-family: Menlo; font-size: 14px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">curl http://localhost:9002/version</span></div>
<div style="font-family: Menlo; font-size: 14px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"></span></div>
<div style="font-family: Menlo; font-size: 14px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"></span></div>
<div style="font-family: Menlo; font-size: 14px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">{"id":1,"content":"new"}</span></div>
<div>
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<br />
Let's assume the old version of the application is deployed to a K8s cluster running on <b>Oracle Kubernetes Engine</b> with the following manifest:<br />
<pre class="java" name="code">
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: superapp
spec:
replicas: 3
template:
metadata:
labels:
app: superapp
spec:
containers:
- name: superapp
image: eugeneflexagon/superapp:old
ports:
- containerPort: 8080
</pre>
So there are three replicas of a pod running the old version of the application. There is also a service routing the traffic to these pods:
<br />
<pre class="java" name="code">apiVersion: v1
kind: Service
metadata:
name: superapp
spec:
selector:
app: superapp
ports:
- port: 8080
targetPort: 8080
</pre>
<div>
<b>Rolling Update</b><br />
This deployment strategy updates pods in a rolling update way, changing them one by one.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-jOJiDi26dFA/XLoetbKw4zI/AAAAAAAACFk/kK8N_QUDYMcTnijicgciSttUOeTeDeWIgCLcBGAs/s1600/Screen%2BShot%2B2019-04-19%2Bat%2B2.16.52%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="92" data-original-width="1294" height="27" src="https://1.bp.blogspot.com/-jOJiDi26dFA/XLoetbKw4zI/AAAAAAAACFk/kK8N_QUDYMcTnijicgciSttUOeTeDeWIgCLcBGAs/s400/Screen%2BShot%2B2019-04-19%2Bat%2B2.16.52%2BPM.png" width="400" /></a></div>
<br />
This is a default strategy handled by a K8s cluster itself, so we just need to update the <b>superapp </b>deployment with a reference to the new image:
<br />
<pre class="java" name="code">apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: superapp
spec:
replicas: 3
template:
metadata:
labels:
app: superapp
spec:
containers:
- name: superapp
image: eugeneflexagon/superapp:new
ports:
- containerPort: 8080
</pre>
However, we can fine-tune the <b>rolling update</b> algorithm by providing parameters for this deployment strategy in the manifest file:<br />
<pre class="java" name="code">spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 30%
maxUnavailable: 30%
template:
...
</pre>
<div>
<br /></div>
The<b> maxSurge</b> parameter defines the maximum number of pods that can be created over the desired number of pods. It can be either a percentage or an absolute number. Default value is 25%.<br />
The <b>maxUnavailable </b>parameter<b> </b>defines the maximum number of pods that can be unavailable during the update process. It can be either a percentage or an absolute number. Default value is 25%.<br />
<br />
<br />
<b>Recreate</b><br />
This deployment strategy kills all old pods and then creates the new ones.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-u_30xD5h8PM/XLow6FqLmkI/AAAAAAAACF0/ag0TYX9DSswK5I2Txn7ibJPVgUagdjgwACLcBGAs/s1600/Screen%2BShot%2B2019-04-19%2Bat%2B3.34.37%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="86" data-original-width="576" height="29" src="https://4.bp.blogspot.com/-u_30xD5h8PM/XLow6FqLmkI/AAAAAAAACF0/ag0TYX9DSswK5I2Txn7ibJPVgUagdjgwACLcBGAs/s200/Screen%2BShot%2B2019-04-19%2Bat%2B3.34.37%2BPM.png" width="200" /></a></div>
<pre class="java" name="code">spec:
replicas: 3
strategy:
type: Recreate
template:
...
</pre>
<br />
Very simple.<br />
<br />
<b>Blue/Green</b><br />
This strategy defines an old version of the application as a <b>green</b> one and a new version as a <b>blue</b> one. Users always have access only to the green version.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-x1HhSyXA11g/XLozcpiw2uI/AAAAAAAACGE/zeNyqZBZIMASHaciucZRvXWZUdGJGD8QwCLcBGAs/s1600/Screen%2BShot%2B2019-04-19%2Bat%2B3.45.23%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="228" data-original-width="222" height="100" src="https://4.bp.blogspot.com/-x1HhSyXA11g/XLozcpiw2uI/AAAAAAAACGE/zeNyqZBZIMASHaciucZRvXWZUdGJGD8QwCLcBGAs/s200/Screen%2BShot%2B2019-04-19%2Bat%2B3.45.23%2BPM.png" width="94" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<pre class="java" name="code">apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: superapp-01
spec:
template:
metadata:
labels:
app: superapp
version: "01"
...
apiVersion: v1
kind: Service
metadata:
name: superapp
spec:
selector:
app: superapp
version: "01"
...
</pre>
<br />
The service routes the traffic only to pods with label <b>version: "01"</b>.<br />
<br />
We deploy a blue version to a K8s cluster and make it available only for QAs or for a test automation tool (via a separate service or direct port-forwarding).<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-Yespjv9K2lA/XLo1MRSc7SI/AAAAAAAACGU/2TCzXV7HCqIQ1TNCkONgEiNZVejx8915gCLcBGAs/s1600/Screen%2BShot%2B2019-04-19%2Bat%2B3.50.43%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="216" data-original-width="238" height="100" src="https://1.bp.blogspot.com/-Yespjv9K2lA/XLo1MRSc7SI/AAAAAAAACGU/2TCzXV7HCqIQ1TNCkONgEiNZVejx8915gCLcBGAs/s200/Screen%2BShot%2B2019-04-19%2Bat%2B3.50.43%2BPM.png" width="120" /></a></div>
<br />
<pre class="java" name="code">apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: superapp-02
spec:
template:
metadata:
labels:
app: superapp
version: "02"
...
</pre>
<div>
<br /></div>
Once the <b>new</b> version is tested we switch the service to it and scale down the <b>old</b> version:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-f-uNdg5PKKQ/XLo1zjiFYRI/AAAAAAAACGc/G-nzAQJIrR8IoE8Dn8Mf8Udpt9OhxBeaACLcBGAs/s1600/Screen%2BShot%2B2019-04-19%2Bat%2B3.54.26%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="206" data-original-width="672" height="98" src="https://3.bp.blogspot.com/-f-uNdg5PKKQ/XLo1zjiFYRI/AAAAAAAACGc/G-nzAQJIrR8IoE8Dn8Mf8Udpt9OhxBeaACLcBGAs/s320/Screen%2BShot%2B2019-04-19%2Bat%2B3.54.26%2BPM.png" width="320" /></a></div>
<br />
<br />
<pre class="java" name="code">apiVersion: v1
kind: Service
metadata:
name: superapp
spec:
selector:
app: superapp
version: "02"
...
</pre>
<div>
<br /></div>
<br />
<pre class="java" name="code">kubectl scale deployment superapp-01 --replicas=0
</pre>
Having done that, all users work with the new version.<br />
<br />
So there is no Istio so far. Everything is handled by a K8s cluster out-of-the box. Let's move on to the next strategy.<br />
<div>
<br /></div>
<br />
<b>Canary</b><br />
I love this deployment strategy as it lets the users test the new version of the application and they don't even know about that. The idea is that we deploy a new version of the application and route 10% of the traffic to it. The users have no idea about that.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-DI5min7RHA8/XLpIHO7PYyI/AAAAAAAACGo/Dc4EHuyUv7I9JbMCYyJXVEnmyyQY7kcUgCLcBGAs/s1600/Screen%2BShot%2B2019-04-19%2Bat%2B5.13.32%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="264" data-original-width="302" height="120" src="https://3.bp.blogspot.com/-DI5min7RHA8/XLpIHO7PYyI/AAAAAAAACGo/Dc4EHuyUv7I9JbMCYyJXVEnmyyQY7kcUgCLcBGAs/s200/Screen%2BShot%2B2019-04-19%2Bat%2B5.13.32%2BPM.png" width="140" /></a></div>
<br />
If it works for a while, we can balance the traffic 70/30, then 50/50 and eventually 0/100.<br />
Even though this strategy can be implemented with K8s resources only by playing with the number of old and new pods, it is way more convenient to implement it with Istio.<br />
So the old and the new applications are defined as the following deployments:<br />
<pre class="java" name="code">apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: superapp-01
spec:
template:
metadata:
labels:
app: superapp
version: "01"
...
</pre>
<div>
<br /></div>
<div>
<pre class="java" name="code">apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: superapp-02
spec:
template:
metadata:
labels:
app: superapp
version: "02"
...</pre>
</div>
<br />
The service routes the traffic to both of them:<br />
<pre class="java" name="code">apiVersion: v1
kind: Service
metadata:
name: superapp
spec:
selector:
app: superapp
...</pre>
<br />
On top of that we are going to use the following Istio resources: <b>VirtualService </b>and <b>DestinationRule.</b><br />
<pre class="java" name="code">apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: superapp
spec:
host: superapp
subsets:
- name: green
labels:
version: "01"
- name: blue
labels:
version: "02"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: superapp
spec:
hosts:
- superapp
http:
- match:
- uri:
prefix: /version
route:
- destination:
port:
number: 8080
host: superapp
subset: green
weight: 90
- destination:
port:
number: 8080
host: superapp
subset: blue
weight: 10</pre>
The VirtualService will route all the traffic coming to the <b>superapp</b> service (hosts) to the <b>green </b>and <b>blue </b>pods according to the provided weights (90/10).<br />
<br />
<b>A/B Testing</b><br />
With this strategy we can precisely control what users, from what devices, departments, etc. are routed to the new version of the application. <br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-2-VtU6rE394/XLtk2KsWLuI/AAAAAAAACG4/VG6ZDHBo9-wrYBSonCKePf6UeK-DDLUtACLcBGAs/s1600/Screen%2BShot%2B2019-04-20%2Bat%2B1.28.23%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="278" data-original-width="380" height="126" src="https://1.bp.blogspot.com/-2-VtU6rE394/XLtk2KsWLuI/AAAAAAAACG4/VG6ZDHBo9-wrYBSonCKePf6UeK-DDLUtACLcBGAs/s200/Screen%2BShot%2B2019-04-20%2Bat%2B1.28.23%2BPM.png" width="180" /></a></div>
<br />
For example here we are going to analyze the request header and if its custom tag <b>"end-user"</b> equals <b>"xammer"</b> it will be routed to the new version of the application. The rest of the requests will be routed to the old one:<br />
<pre class="java" name="code">apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: superapp
spec:
gateways:
- superapp
hosts:
- superapp
http:
- match:
- headers:
end-user:
exact: xammer
route:
- destination:
port:
number: 8080
host: superapp
subset: blue
- route:
- destination:
port:
number: 8080
host: superapp
subset: green</pre>
<br />
All examples and manifest files for this post are available on <a href="https://github.com/eedorenko/k8sistiodeploymentstrategies">GitHub</a> so you can play with various strategies and sophisticated routing rules on your own. You just need a K8s cluster (e.g. Minikube on your laptop) with preinstalled Istio. Happy deploying!<br />
<br />
That's it!</div>
</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-67849383839362406152019-04-13T14:25:00.001-07:002019-04-13T15:04:52.468-07:00Load Testing of a Microservice. Kubernetes way.<div dir="ltr" style="text-align: left;" trbidi="on">
Let's assume there is a <b>microservice</b> represented by a composition of containers running on a <b>K8s cluster</b> somewhere in a <b>cloud</b>, e.g. <b>Oracle Kubernetes Engine (OKE)</b>. At some point we want to quickly stress test a specific microservice component or the entire microservice. So we want to know how it works under the load, how it handles many subsequent requests coming from many parallel clients. The good news is that we have already a tool for that. Up and running. This is the Kubernetes cluster itself.<br />
<br />
We're going to use <b>Kubernetes Job</b> for this testing described in the following manifest file:<br />
<pre class="java" name="code">apiVersion: batch/v1
kind: Job
metadata:
name: job-load
spec:
parallelism: 50
template:
spec:
containers:
- name: loader
image: eugeneflexagon/aplpine-with-curl:1.0.0
command: ["time", "curl", "http://my_service:8080/my_path?[1-100]"]
restartPolicy: OnFailure
</pre>
This job is going to spin up <b>50 pods</b> running in parallel and sending <b>100 requests</b> each to <b>my_service</b> on port <b>8080 </b>and with path <b>my_path. </b>Having the job created and started by invoking<br />
<pre class="java" name="code">kubectl apply -f loadjob.yaml</pre>
We can observe all 50 pods created by the job using<br />
<br />
kubectl get pods -l job-name=job-load<br />
<pre class="java" name="code">NAME READY STATUS RESTARTS AGE
job-load-4n262 1/2 Completed 1 12m
job-load-dsqtc 1/2 Completed 1 12m
job-load-khdn4 1/2 Completed 1 12m
job-load-kptww 1/2 Completed 1 12m
job-load-wf9pd 1/2 Completed 1 12m
...
</pre>
If we look at the logs of any of these pods
<br />
<br />
<pre class="java" name="code">kubectl logs job-load-4n262
</pre>
We'll see something like the following:
<br />
<pre class="java" name="code">[1/100]: http://my_service.my_namespace:8080/my_path?1 --> <stdout>
{"id":456,"content":"Hello world!"}
[2/100]: http://my_service.my_namespace:8080/my_path?2 --> <stdout>
{"id":457,"content":"Hello world!"}
[3/100]: http://my_service.my_namespace:8080/my_path?3 --> <stdout>
{"id":458,"content":"Hello world!"}
....
real 0m 10.04s
user 0m 0.00s
sys 0m 0.04s
</pre>
<div>
That's it!</div>
</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-36277870875163615622019-03-08T12:31:00.000-08:002019-03-09T14:58:52.041-08:00Serverless API with Azure Functions<div dir="ltr" style="text-align: left;" trbidi="on">
In this post I am going to work on a pretty simple use case. While executing a deployment pipeline <a href="http://flexagon.com/flexdeploy/">FlexDeploy</a> may produce some human tasks that should be either approved or rejected. For example, someone has to approve a deployment to the production environment. It can be done either in <b>FlexDeploy UI</b> or with some external communication channels. Today I am going to focus on the scenario when a <b>FlexDeploy</b> human task is approved/rejected with <b><a href="http://slack.com/">Slack</a></b>:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-t5QZzBkhOmM/XILFLaGvLrI/AAAAAAAACBw/86mPSbZjiI4RA8HD1-UPkmijl8GKhcFaQCLcBGAs/s1600/Screen%2BShot%2B2019-03-08%2Bat%2B1.40.00%2BPM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="290" data-original-width="1536" height="120" src="https://3.bp.blogspot.com/-t5QZzBkhOmM/XILFLaGvLrI/AAAAAAAACBw/86mPSbZjiI4RA8HD1-UPkmijl8GKhcFaQCLcBGAs/s640/Screen%2BShot%2B2019-03-08%2Bat%2B1.40.00%2BPM.png" width="640" /></a></div>
<br />
There are a few requirements and considerations that I would like to take into account:<br />
<br />
<ul style="text-align: left;">
<li>I don't want to teach <b>FlexDeploy</b> to communicate with <b>Slack</b></li>
<li>I don't want to provide <b>Slack</b> with the details of <b>FlexDeploy API</b></li>
<li>I don't want to expose <b>FlexDeploy API</b> to public </li>
<li>I do want to be able to easily change <b>Slack</b> to something different or add other communication tools without touching <b>FlexDeploy</b></li>
</ul>
<div>
Basically, I want to decouple <b>FlexDeploy</b> from the details of the external communication mechanism. For that reason I am going to introduce an extra layer, an <b>API</b> between <b>FlexDeploy</b> and <b>Slack</b>. It looks like <b>serverless paradigm</b> is a very attractive approach to implement this <b>API</b>. Today I am going to build it with <b><a href="https://docs.microsoft.com/en-us/azure/azure-functions/">Azure Functions</a>,</b> because ... why not? </div>
<div>
<br /></div>
<div>
So, technically, a poc version of the solution looks like this:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-8bdteWR3w50/XILHEfbhlLI/AAAAAAAACCE/9kiFRCU-8V82SS00EeGz75eonQahF3KtgCLcBGAs/s1600/Screen%2BShot%2B2019-03-08%2Bat%2B1.48.15%2BPM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="1166" data-original-width="1600" height="466" src="https://2.bp.blogspot.com/-8bdteWR3w50/XILHEfbhlLI/AAAAAAAACCE/9kiFRCU-8V82SS00EeGz75eonQahF3KtgCLcBGAs/s640/Screen%2BShot%2B2019-03-08%2Bat%2B1.48.15%2BPM.png" width="640" /></a></div>
<div>
<br /></div>
<div>
Once a new human task comes up, FlexDeploy notifies the serverless API about that providing an internal task id and task description. There is a function <b>SaveTask</b> that saves the provided task details along with a generated token (just some uid) to <b><a href="https://azure.microsoft.com/en-us/services/storage/tables/?&OCID=AID719825_SEM_Lyij5NkP&lnkd=Google_Azure_Brand&gclid=EAIaIQobChMI2df_wK7z4AIVkbrACh2PVg0kEAAYASAAEgL1SPD_BwE">Azure Table storage</a></b>. This token has an expiration time meaning that it should be used before that time to approve/reject the task.<br />
<br />
<pre class="brush: js" name="code">const azure = require('azure-storage');
const uuidv1 = require('uuid/v1');
module.exports = async function (context, taskid) {
var tableSvc = azure.createTableService('my_account', 'my_key');
var entGen = azure.TableUtilities.entityGenerator;
var token = uuidv1();
var tokenEntity = {
PartitionKey: entGen.String('tokens'),
RowKey: entGen.String(token),
TaskId: entGen.String(taskid),
dueDate: entGen.DateTime(new Date(Date.now() + 24 * 60 * 60 * 1000))
};
tableSvc.insertEntity('tokens',tokenEntity, function (error, result, response) { });
return token;
};
</pre>
<br />
Having the token saved, the <b>PostToSlack </b>function is invoked posting a message to a <b>Slack</b> channel. <b>SaveTask </b>and <b>PostTo</b><b>Slack </b>functions are orchestrated into a <a href="https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview">durable function</a> <b>NotifyOnTask </b>which is actually being invoked by <b>FlexDeploy:</b></div>
<div>
<pre class="brush: js" name="code">const df = require("durable-functions");
module.exports = df.orchestrator(function*(context){
var task = context.df.getInput()
var token = yield context.df.callActivity("SaveTask", task.taskid)
return yield context.df.callActivity("PostToSlack", {"token": token, "description": task.description})
});
</pre>
The message in <b>Slack </b>contains two buttons to approve and reject the task.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-ZaXZdcrNl1g/XIRAXOTuADI/AAAAAAAACCc/x8HU7nw7Qi4kGeTFrks4Np-vDXvtu2IoACLcBGAs/s1600/Screen%2BShot%2B2019-03-09%2Bat%2B4.38.04%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="184" data-original-width="864" height="85" src="https://1.bp.blogspot.com/-ZaXZdcrNl1g/XIRAXOTuADI/AAAAAAAACCc/x8HU7nw7Qi4kGeTFrks4Np-vDXvtu2IoACLcBGAs/s400/Screen%2BShot%2B2019-03-09%2Bat%2B4.38.04%2BPM.png" width="400" /></a></div>
<br />
The buttons refer to webhooks pointing to the <b>ActionOnToken </b>durable function:<br />
<pre class="brush: js" name="code">const df = require("durable-functions");
module.exports = df.orchestrator(function*(context){
var input = context.df.getInput()
var taskId = yield context.df.callActivity("GetTaskId", input.token)
if (input.action == 'approve') {
yield context.df.callActivity("ApproveTask", taskId)
} else if (input.action == 'reject') {
yield context.df.callActivity("RejectTask", taskId)
}
});
</pre>
<b><br /></b>
<b>ActionOnToken </b>invokes <b>GetTaskId </b>function retrieving task id from the storage by the given token:</div>
<div>
<pre class="brush: js" name="code">const azure = require('azure-storage');
module.exports = async function (context, token) {
var tableSvc = azure.createTableService('my_account', 'my_key');
function queryTaskID(token) {
return new Promise(function (resolve, reject) {
tableSvc.retrieveEntity('tokens', 'tokens', token,
function (error, result, response) {
if (error) {
reject(error)
} else {
resolve(result)
}
});
});
}
var tokenEntity = await queryTaskID(token);
if (tokenEntity) {
var dueDate = tokenEntity.dueDate._
if (dueDate > Date.now()) {
return tokenEntity.TaskId._
}
}
};</pre>
</div>
<div>
Having done that it either approves or rejects the task by invoking either <b>ApproveTask</b> or <b>RejectTask</b> functions. These functions in their turn make corresponding calls to FlexDeploy REST API.<br />
<pre class="brush: js" name="code">const request = require('sync-request');
const fd_url = 'http://dkrlp01.flexagon:8000';
module.exports = async function (context, taskid) {
var taskid = taskid;
var res = request('PUT',
fd_url+'/flexdeploy/rest/v1/tasks/approval/approve/'+taskid,{
});
};</pre>
</div>
<div>
<br /></div>
<div>
I could start developing my serverless application directly in the cloud on <b>Azure Portal</b>, but I decided to implement everything and play with it locally and move to the cloud later. The fact that I can do that, develop and test my functions locally is actually very cool, not every serverless platform gives you that feature.<b> </b>The only thing I have configured in the cloud is <b>Azure Table</b> <b>storage </b>account with a table to store my tokens and task details. </div>
<div>
<br /></div>
<div>
A convenient way to start working with <b>Azure Functions</b> locally is to use <b><a href="https://code.visualstudio.com/">Visual Studio Code</a></b> as a development tool. I am working on Mac, so I have downloaded and installed a version for Mac OS X. VS Code is all about extensions, for every technology you are working with you are installing one or a few extensions. Same is about <b>Azure Functions. </b>There is an extension for that:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-WCZ-LfwOQAg/XIKrXrwiCNI/AAAAAAAACA8/83hFemyMx6gVuK50NMp29tNk2ArC7HfnwCLcBGAs/s1600/Screen%2BShot%2B2019-03-08%2Bat%2B11.50.06%2BAM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="587" data-original-width="1600" height="233" src="https://4.bp.blogspot.com/-WCZ-LfwOQAg/XIKrXrwiCNI/AAAAAAAACA8/83hFemyMx6gVuK50NMp29tNk2ArC7HfnwCLcBGAs/s640/Screen%2BShot%2B2019-03-08%2Bat%2B11.50.06%2BAM.png" width="640" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
Having done that, you are getting a new tab where you can create a new function application and start implementing your functions:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-ZF8yiuOQ2Is/XIKse7QJMII/AAAAAAAACBI/eaVRIYjF2_US7nYiaz9kTO7OVKQtUq-zgCLcBGAs/s1600/Screen%2BShot%2B2019-03-08%2Bat%2B11.53.25%2BAM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="750" data-original-width="1374" height="348" src="https://4.bp.blogspot.com/-ZF8yiuOQ2Is/XIKse7QJMII/AAAAAAAACBI/eaVRIYjF2_US7nYiaz9kTO7OVKQtUq-zgCLcBGAs/s640/Screen%2BShot%2B2019-03-08%2Bat%2B11.53.25%2BAM.png" width="640" /></a></div>
<div>
</div>
<div>
<br /></div>
<div>
While configuring a new project the wizard is asking you to select a language you prefer to implement the functions with:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-Rpsm8uAT10A/XIKvokM4qpI/AAAAAAAACBU/HPUknmSWepUjm9s8Q9X1kyFccxTE-TXagCLcBGAs/s1600/Screen%2BShot%2B2019-03-08%2Bat%2B11.58.24%2BAM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="432" data-original-width="1508" height="182" src="https://2.bp.blogspot.com/-Rpsm8uAT10A/XIKvokM4qpI/AAAAAAAACBU/HPUknmSWepUjm9s8Q9X1kyFccxTE-TXagCLcBGAs/s640/Screen%2BShot%2B2019-03-08%2Bat%2B11.58.24%2BAM.png" width="640" /></a></div>
<div>
<br /></div>
<div>
Even though I love Java, I have selected JavaScript because on top of regular functions I wanted to implement <a href="https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview">durable functions</a> and they support <b>C#</b>, <b>F#</b> and <b>JavaScript</b> only. At the moment of writing this post <b>JavaScript</b> was the closest to me.</div>
<div>
<br /></div>
<div>
Th rest is as usual. You create functions, write the code, debug, test, fix, and all over again. You just click F5 and VS Code starts the entire application in debug mode for you:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-2TPDe-3drdc/XILQVTwD1II/AAAAAAAACCQ/aZKZVY07KnQzxupe-FP6c9D4OzlBgqZRwCLcBGAs/s1600/Screen%2BShot%2B2019-03-08%2Bat%2B2.27.55%2BPM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="1078" data-original-width="1600" height="430" src="https://1.bp.blogspot.com/-2TPDe-3drdc/XILQVTwD1II/AAAAAAAACCQ/aZKZVY07KnQzxupe-FP6c9D4OzlBgqZRwCLcBGAs/s640/Screen%2BShot%2B2019-03-08%2Bat%2B2.27.55%2BPM.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div>
When you start the application for the first time, VS Code will propose you to install the functions runtime on your computer if it is not there. So basically, assuming that you have on your laptop runtime of your preferred language (Node.js), you just need to have VS Code with the functions extension to start working with <b>Azure Functions</b>. It will do the rest of installations for you. </div>
<div>
<br /></div>
<div>
So, once the application is started I can test it by invoking <b>NotifyOnTask</b> function which initiates the entire cycle:</div>
<div>
<pre class="java" name="code">curl -X POST --data '{"taskid":"8900","description":"DiPocket v.1.0.0.1 is about to be deployed to PROD"}' -H "Content-type: application/json" http://localhost:7071/api/orchestrators/NotifyOnTask
</pre>
</div>
<div>
<br /></div>
<div>
The source code of the application is available on <a href="https://github.com/eedorenko/azure-fd-slack-api">GitHub</a>.<br />
<br />
Well, the general opinion of <b>Azure Functions</b> so far is ... it is good. It just works. I didn't run into any annoying issue (so far) while implementing this solution (except some stupid mistakes that I made because I didn't read the manual carefully). I will definitely keep playing and posting on <b>Azure Functions </b>enriching and moving this solution to the cloud and, probably, implementing something different.</div>
<div>
<br /></div>
<div>
That's it!</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-88294060498684577642019-02-22T12:26:00.000-08:002019-02-22T12:44:29.054-08:00Conversational UI with Oracle Digital Assistant and Fn Project. Part III. Moving to the cloud.<div dir="ltr" style="text-align: left;" trbidi="on">
<div>
In this post I am going to continue the story of implementing a conversational UI for <a href="https://flexagon.com/flexdeploy/">FlexDeploy</a> on top of <a href="https://cloud.oracle.com/digital-assistant">Oracle Digital Assistant</a> and <a href="https://fnproject.io/">Fn Project</a>. Today I am going to move the serverless API working around my chatbot to the cloud, so the entire solution is working in the cloud:</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-67oTcg1gpQ0/XHBQQZOjigI/AAAAAAAACAM/yAls9s1PNHYyn30wuaAQg1N-STGs-PibACLcBGAs/s1600/Screen%2BShot%2B2019-02-22%2Bat%2B1.28.22%2BPM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="846" data-original-width="1600" height="337" src="https://1.bp.blogspot.com/-67oTcg1gpQ0/XHBQQZOjigI/AAAAAAAACAM/yAls9s1PNHYyn30wuaAQg1N-STGs-PibACLcBGAs/s640/Screen%2BShot%2B2019-02-22%2Bat%2B1.28.22%2BPM.png" width="640" /></a></div>
<br />
<br />
The API is implemented as a set of Fn functions collected into an Fn application. The beauty of Fn is that it's just a bunch of Docker containers that can equally run on your laptop on your local Docker engine and somewhere in the cloud. Having said that I can run my Fn application on a K8s cluster from any cloud provider as it is described <a href="http://adfpractice-fedor.blogspot.com/2018/03/run-fn-functions-on-k8s-on-google-cloud.html">here</a>. But today is not that day. Today I am going to run my serverless API on a brand new cloud service <a href="https://blogs.oracle.com/cloud-infrastructure/announcing-oracle-functions">Oracle Functions</a> which is built on top of Fn. The service is not general available yet, but I participate in the Limited Availability program so I have a trial access to it, I can play with it and blog about it. In this solution I had to get rid of the Fn Flow implemented <a href="http://adfpractice-fedor.blogspot.com/2018/12/conversational-ui-with-oracle-digital.html">here</a> and get back to my original implementation as Fn Flow is not supported by Oracle Functions yet. I hope it will be soon as this is actually the best part.</div>
<div>
<br /></div>
<div>
So, having our OCI environment configured and having <b>Oracle Functions</b> service up and running (I am not reposting Oracle tutorial on that here), we need to configure our Fn CLI to be able to communicate with the service:</div>
<pre class="java" name="code">fn create context oracle_fn --provider oracle
fn use context oracle_fn
fn update context oracle.compartment-id MY_COMPARTMENT_ID
fn update context api-url https://functions.us-phoenix-1.oraclecloud.com
fn update context registry phx.ocir.io/flexagonoraclecloud/flexagon-repo
fn update context oracle.profile oracle_fn
</pre>
<div>
<br /></div>
<div>
Ok, so now our Fn command line interface is talking to <b>Oracle Functions</b>. The next step is to create an application in the <b>Oracle Functions</b> console:<br />
<br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-hfDgi7eKGt4/XF9RNWF5CzI/AAAAAAAAB_M/2mJ98H6Jeq4puFTidS-KHHBjUPy8tuEVgCLcBGAs/s1600/Screen%2BShot%2B2019-02-09%2Bat%2B4.16.00%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="789" data-original-width="1600" height="196" src="https://2.bp.blogspot.com/-hfDgi7eKGt4/XF9RNWF5CzI/AAAAAAAAB_M/2mJ98H6Jeq4puFTidS-KHHBjUPy8tuEVgCLcBGAs/s400/Screen%2BShot%2B2019-02-09%2Bat%2B4.16.00%2BPM.png" width="400" /></a></div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
Now we can deploy the <b>Fn application</b> to <b>Oracle Functions</b>:</div>
<div>
<pre class="java" name="code">Eugenes-MacBook-Pro-3:fn fedor$ ls -l
total 8
-rw-r--r--@ 1 fedor staff 12 Dec 4 15:41 app.yaml
drwxr-xr-x 5 fedor staff 160 Feb 9 15:24 createsnapshotfn
drwxr-xr-x 6 fedor staff 192 Feb 9 15:25 receiveFromBotFn
drwxr-xr-x 6 fedor staff 192 Feb 9 15:25 sendToBotFn
Eugenes-MacBook-Pro-3:fn fedor$
Eugenes-MacBook-Pro-3:fn fedor$
Eugenes-MacBook-Pro-3:fn fedor$ fn deploy --all
</pre>
Having done that we can observe the application in the <b>Oracle Functions</b> console:</div>
<div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-lnNopDilG7o/XHBZBfBz7-I/AAAAAAAACAc/5Ye2VUy-XGs5RUNz2xfgv5ffLc14F8r7ACLcBGAs/s1600/Screen%2BShot%2B2019-02-22%2Bat%2B2.18.02%2BPM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="832" data-original-width="1600" height="331" src="https://3.bp.blogspot.com/-lnNopDilG7o/XHBZBfBz7-I/AAAAAAAACAc/5Ye2VUy-XGs5RUNz2xfgv5ffLc14F8r7ACLcBGAs/s640/Screen%2BShot%2B2019-02-22%2Bat%2B2.18.02%2BPM.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
</div>
<div>
The next step is to update API urls in the chatbot and on my laptop so the functions in the cloud are invoked instead of the previous local implementation. The urls can be retrieved with the following command:<br />
</div>
<pre class="java" name="code">
fn list triggers odaapp
</pre>
<div>
So far the migration from my laptop to <b>Oracle Functions</b> has been looking pretty nice and easy. But here is a little of pain. In order to invoke functions hosted in <b>Oracle Functions</b> with http requests, the requests should be signed so they can pass through the authentication. A node.js implementation of invoking a signed function call looks like this:<br />
<pre class="java" name="code">var fs = require('fs');
var https = require('https');
var os = require('os');
var httpSignature = require('http-signature');
var jsSHA = require("jssha");
var tenancyId = "ocid1.tenancy.oc1..aaaaaaaayonz5yhpr4vxqpbdof5rn7x5pfrlgjwjycwxasf4dkexiq";
var authUserId = "ocid1.user.oc1..aaaaaaaava2e3wd3cu6lew2sktd6by5hnz3d7prpgjho4oambterba";
var keyFingerprint = "88:3e:71:bb:a5:ea:68:b7:56:fa:3e:5d:ea:45:60:10";
var privateKeyPath = "/Users/fedor/.oci/functions_open.pem";
var privateKey = fs.readFileSync(privateKeyPath, 'ascii');
var identityDomain = "identity.us-ashburn-1.oraclecloud.com";
function sign(request, options) {
var apiKeyId = options.tenancyId + "/" + options.userId + "/" + options.keyFingerprint;
var headersToSign = [
"host",
"date",
"(request-target)"
];
var methodsThatRequireExtraHeaders = ["POST", "PUT"];
if(methodsThatRequireExtraHeaders.indexOf(request.method.toUpperCase()) !== -1) {
options.body = options.body || "";
var shaObj = new jsSHA("SHA-256", "TEXT");
shaObj.update(options.body);
request.setHeader("Content-Length", options.body.length);
request.setHeader("x-content-sha256", shaObj.getHash('B64'));
headersToSign = headersToSign.concat([
"content-type",
"content-length",
"x-content-sha256"
]);
}
httpSignature.sign(request, {
key: options.privateKey,
keyId: apiKeyId,
headers: headersToSign
});
var newAuthHeaderValue = request.getHeader("Authorization").replace("Signature ", "Signature version=\"1\",");
request.setHeader("Authorization", newAuthHeaderValue);
}
function handleRequest(callback) {
return function(response) {
var responseBody = "";
response.on('data', function(chunk) {
responseBody += chunk;
});
response.on('end', function() {
callback(JSON.parse(responseBody));
});
}
}
function createSnapshot(release) {
var body = release;
var options = {
host: 'af4qyj7yhva.us-phoenix-1.functions.oci.oraclecloud.com',
path: '/t/createsnapshotfn',
method: 'POST',
headers: {
"Content-Type": "application/text",
}
};
var request = https.request(options, handleRequest(function(data) {
console.log(data);
}));
sign(request, {
body: body,
privateKey: privateKey,
keyFingerprint: keyFingerprint,
tenancyId: tenancyId,
userId: authUserId
});
request.end(body);
};</pre>
</div>
<div>
<br /></div>
<div>
This approach should be used by <b>Oracle Digital Assistant</b> custom components and by the <b>listener </b>component on my laptop while invoking the serverless API hosted in <b>Oracle Functions</b>.<br />
<br />
<br />
<br /></div>
<div>
That's it!</div>
</div>Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-61393137938727737192019-01-27T12:13:00.000-08:002019-02-17T12:19:59.002-08:00Monitoring an ADF Application in a Docker Container. Easy Way.<div dir="ltr" style="text-align: left;" trbidi="on">
In this short post I am going to show a simple approach to make sure that your ADF application running inside a Docker container is a healthy Java application in terms of memory utilization. I am going to use a standard tool JConsole which comes as a part of JDK installation on your computer. If there is a problem (i.e. a memory leak, often GCs, long GCs, etc.) you will see it with JConsole. In an effort to analyze the root of the problem and find the solution you might want to use more powerful and fancy tools. I will discuss that in one of my following posts. A story of tuning JVM for an ADF application is available <a href="https://www.slideshare.net/euegenefedorenko/oow2013-ef-final43">here</a>.<br />
<br />
So there is an ADF application running on top of Tomcat. The application and the Tomcat are packaged into a Docker container running on <b>dkrlp01.flexagon </b>host. <a href="https://www.slideshare.net/EugeneFedorenko4/adf-with-docker?ref=">There are</a> some slides on running an ADF application in a Docker container.<br />
In order to connect with JConsole from my laptop to a JVM running inside the container, we need to add the following JVM arguments in <b>tomcat/bin/setenv.sh</b>:<br />
<pre class="java" name="code"> -Dcom.sun.management.jmxremote=true
-Dcom.sun.management.jmxremote.rmi.port=9010
-Dcom.sun.management.jmxremote.port=9010
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.local.only=false
-Djava.rmi.server.hostname=dkrlp01.flexagon
</pre>
Besides that the container has to expose port 9010, so it should be created with<br />
<b>"docker run -p 9010:9010 ..." </b>command.<br />
<br />
Having done that we can invoke <b>jconsole </b>command locally and connect to the container:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-I3UnrJm4EG4/XGnAfTGNkrI/AAAAAAAAB_8/ItFWJwqfS-slGO1CvHbat3m9YzBvOQc2wCLcBGAs/s1600/Screen%2BShot%2B2019-02-17%2Bat%2B2.13.35%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="722" data-original-width="832" height="277" src="https://1.bp.blogspot.com/-I3UnrJm4EG4/XGnAfTGNkrI/AAAAAAAAB_8/ItFWJwqfS-slGO1CvHbat3m9YzBvOQc2wCLcBGAs/s320/Screen%2BShot%2B2019-02-17%2Bat%2B2.13.35%2BPM.png" width="320" /></a></div>
<br />
Now just give the application some load with you favorite testing tool (JMeter, OATS, SOAP UI, Selenium, etc..) and observe the memory utilization:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-MRkjGDlXtso/XGnAHwX8zVI/AAAAAAAAB_0/tcNqBl-yW4UYD-fIRiwxEQ6Q6cPVbossACLcBGAs/s1600/Screen%2BShot%2B2019-02-17%2Bat%2B2.11.47%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1095" data-original-width="1600" height="273" src="https://1.bp.blogspot.com/-MRkjGDlXtso/XGnAHwX8zVI/AAAAAAAAB_0/tcNqBl-yW4UYD-fIRiwxEQ6Q6cPVbossACLcBGAs/s400/Screen%2BShot%2B2019-02-17%2Bat%2B2.11.47%2BPM.png" width="400" /></a></div>
<br />
<br />
That's it!<br />
<br />
<br />
<br />
<br /></div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-17741191893510134862018-12-31T08:38:00.000-08:002019-01-26T09:52:23.775-08:00Conversational UI with Oracle Digital Assistant and Fn Project. Part II<div dir="ltr" style="text-align: left;" trbidi="on">
In my <a href="http://adfpractice-fedor.blogspot.com/2018/11/conversational-ui-with-oracle-digital.html">previous post</a> I implemented a conversational UI for <b><a href="https://flexagon.com/flexdeploy/">FlexDeploy</a></b> with <b><a href="https://cloud.oracle.com/digital-assistant">Oracle Digital Assistant</a></b>. Today I am going to enrich it with Fn Flow so that the chatbot accepts <b>release name</b> instead of <b>id</b> to create a snapshot. Having done that the conversation will sound more natural:<br />
<br />
...<br />
<span style="color: #222222; font-family: "arial" , "helvetica" , sans-serif; font-size: 13.199999809265137px;"><b>"Can you build a snapshot?"</b> I asked.</span><br />
<span style="color: #222222; font-family: "arial" , "helvetica" , sans-serif; font-size: 13.199999809265137px;"><b>"Sure, what release are you thinking of?"</b></span><br />
<span style="color: #222222; font-family: "arial" , "helvetica" , sans-serif; font-size: 13.199999809265137px;"><b>"Olympics release"</b></span><br />
<span style="color: #222222; font-family: "arial" , "helvetica" , sans-serif; font-size: 13.199999809265137px;"><b>"Created a snapshot for release </b></span><b style="caret-color: rgb(34, 34, 34); color: #222222; font-family: arial, helvetica, sans-serif; font-size: 13.199999809265137px;">Olympics</b><span style="color: #222222; font-family: "arial" , "helvetica" , sans-serif; font-size: 13.199999809265137px;"><b>"</b> she reported.</span><br />
<span style="color: #222222; font-family: "arial" , "helvetica" , sans-serif; font-size: 13.199999809265137px;">...</span><br />
<span style="color: #222222; font-family: "arial" , "helvetica" , sans-serif; font-size: 13.199999809265137px;"><br /></span>
<span style="color: #222222; font-family: "arial" , "helvetica" , sans-serif; font-size: 13.199999809265137px;"><br /></span>
The chatbot invokes Fn Flow passing the <b>release name</b> to it as an input. The flow invokes an Fn function to get <b>id </b>of the given release and then it invokes an Fn function calling FlexDeploy Rest API with that <b>id</b>.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-Q3riIlvo7R4/XEyQfGhk8uI/AAAAAAAAB-E/tKdnHHG6Mr8OLaq13Oz8FSp_9Olrrq5NwCLcBGAs/s1600/Screen%2BShot%2B2019-01-26%2Bat%2B10.51.53%2BAM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="846" data-original-width="1516" height="356" src="https://3.bp.blogspot.com/-Q3riIlvo7R4/XEyQfGhk8uI/AAAAAAAAB-E/tKdnHHG6Mr8OLaq13Oz8FSp_9Olrrq5NwCLcBGAs/s640/Screen%2BShot%2B2019-01-26%2Bat%2B10.51.53%2BAM.png" width="640" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
So the <b>createSnapshotFlow </b>orchestrates two Fn functions in a chain. The one getting release <b>id </b>for the given name with FlexDeploy REST API:<br />
<pre class="brush: js" name="code">fdk.handle(function (input) {
var res = request('GET', fd_url + '/flexdeploy/rest/v1/release?releaseName=' + input, {
});
return JSON.parse(res.getBody('utf8'))[0].releaseId;
})
</pre>
<div>
<br /></div>
And the one creating a snapshot for the release <b>id </b>with the same API<b>: </b><br />
<br />
<pre class="brush: js" name="code">fdk.handle(function (input) {
var res = request('POST', fd_url + '/flexdeploy/rest/v1/releases/'+input+'/snapshot', {
json: { action: 'createSnapshot' },
});
return JSON.parse(res.getBody('utf8'));
})
</pre>
<div>
<br /></div>
The core piece of this approach is Fn Flow. The Java code of createSnapshotFlow looks like this:<br />
<br />
<pre class="java" name="code">public class CreateSnapshotFlow {
public byte[] createSnapshot(String input) {
Flow flow = Flows.currentFlow();
FlowFuture<byte[]> stage = flow
//invoke checkreleasefn
.invokeFunction("01D14PNT7ZNG8G00GZJ000000D", HttpMethod.POST,
Headers.emptyHeaders(), input.getBytes())
.thenApply(HttpResponse::getBodyAsBytes)
.thenCompose(releaseId -> flow.
//invoke createsnapshotfn
invokeFunction("01CXRE2PBANG8G00GZJ0000001", HttpMethod.POST,
Headers.emptyHeaders(), releaseId))
.thenApply(HttpResponse::getBodyAsBytes);
return stage.get();
}
</pre>
<br />
<br />
<div>
<br /></div>
Note, that the flow operates with function ids rather than function names. The list of all application functions with their ids can be retrieved with this command line:<br />
<div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-9duuqKDyW9s/XEyeRhUhDwI/AAAAAAAAB-Q/Gb1c5ws7tSwN-uOXCKdn6-YsIDh7u-ExACLcBGAs/s1600/Screen%2BShot%2B2019-01-26%2Bat%2B11.51.47%2BAM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="298" data-original-width="1292" height="145" src="https://2.bp.blogspot.com/-9duuqKDyW9s/XEyeRhUhDwI/AAAAAAAAB-Q/Gb1c5ws7tSwN-uOXCKdn6-YsIDh7u-ExACLcBGAs/s640/Screen%2BShot%2B2019-01-26%2Bat%2B11.51.47%2BAM.png" width="640" /></a></div>
<br />
Where <b>odaapp </b>is my Fn application.<br />
<br />
That's it!</div>
</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-65390788747883973342018-11-30T12:25:00.000-08:002018-12-06T16:51:42.672-08:00Conversational UI with Oracle Digital Assistant and Fn Project<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
Here and there we see numerous predictions that pretty soon chatbots will play a key role in the communication between the users and their systems. I don't have a crystal ball and I don't want to wait for this "pretty soon", so I decided to make these prophecies come true now and see what it looks like.<br />
<br />
A flagman product of the company I am working for is <a href="https://flexagon.com/flexdeploy/">FlexDeploy</a> which is a fully automated DevOps solutions. One of the most popular activities in FlexDeploy is creating a release snapshot that actually builds all deployable artifacts and deploys them across environments with a pipeline.<br />
So, I decided to have some fun over the weekend and implemented a conversational UI for this operation where I am able to <b>talk </b>to FlexDeploy. <b>Literally</b>. At the end of my work my family saw me talking to my laptop and they could hear something like that:<br />
<br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"> <b>"Calypso!"</b> I said.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"> <b>"Hi, how can I help you?"</b> was the answer.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"> <b>"Not sure"</b> I tested her.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"> <b>"You gotta be kidding me!"</b> she got it.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"> <b>"Can you build a snapshot?"</b> I asked.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"> <b>"Sure, what release are you thinking of?"</b></span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"> <b>"1001"</b></span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"> <b>"Created a snapshot for release 1001"</b> she reported.</span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"> <b>"Thank you" </b></span><br />
<span style="font-family: "arial" , "helvetica" , sans-serif;"> <b>"Have a nice day"</b> she said with relief.</span><br />
<span style="font-family: "verdana" , sans-serif;"><br /></span>
So, basically, I was going to implement the following diagram:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-SlK6pamC1SA/XAWeMPp4zHI/AAAAAAAAB7k/4aoTJtjfwwcbIrlkthyJTRWSWwJQ_-OWACLcBGAs/s1600/Screen%2BShot%2B2018-12-03%2Bat%2B3.20.20%2BPM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="259" data-original-width="899" height="184" src="https://2.bp.blogspot.com/-SlK6pamC1SA/XAWeMPp4zHI/AAAAAAAAB7k/4aoTJtjfwwcbIrlkthyJTRWSWwJQ_-OWACLcBGAs/s640/Screen%2BShot%2B2018-12-03%2Bat%2B3.20.20%2BPM.png" width="640" /></a></div>
<br />
As a core component of my UI I used a brand new Oracle product <a href="https://cloud.oracle.com/digital-assistant"><b>Oracle Digital Assistant</b></a>. I built a new <b><a href="https://docs.oracle.com/en/cloud/paas/autonomous-digital-assistant-cloud/use-chatbot/skills-ada.html">skill</a> </b>capable of basic chatting and implemented a new <a href="https://docs.oracle.com/en/cloud/paas/autonomous-digital-assistant-cloud/use-chatbot/bot-components.html#GUID-268C35B3-57B3-4E52-BEEC-1DEE7CA0ACFB"><b>custom</b> </a><b><a href="https://docs.oracle.com/en/cloud/paas/autonomous-digital-assistant-cloud/use-chatbot/bot-components.html#GUID-268C35B3-57B3-4E52-BEEC-1DEE7CA0ACFB">component</a> </b>so my bot was able to invoke an http request to have the backend system create a snapshot. The export of the skill <b>FlexDeployBot</b> along with Node.js source code of the custom component <b>custombotcomponent </b>is available on <a href="https://github.com/eedorenko/flexdeploybot">GitHub</a> repo for this post.<br />
I used my MacBook as a communication device capable of listening and speaking and I defined a <a href="https://docs.oracle.com/en/cloud/paas/autonomous-digital-assistant-cloud/use-chatbot/channels-topic.html#GUID-96CCA06D-0432-4F20-8CDD-E60161F46680"><b>webhook</b> <b>channel</b></a> for my bot so I can send messages to it and get callbacks with responses.<br />
<br />
It looks simple and nice on the diagram above. The only thing is that I wanted to decouple the brain, my chatbot, from the details of the communication device and from the details of the installation/version of my back-end system FlexDeploy. I needed an intermediate API layer, a buffer, something to put between <b>ODA </b>and the outer world. It looks like <b>Serverless Functions </b>is a perfect fit for this job.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-RnBe7nKtP1E/XAWqd0szkZI/AAAAAAAAB7w/QbPO5-Qhh8AFr4JctSE97lWyPuN8GDcAgCLcBGAs/s1600/Screen%2BShot%2B2018-12-03%2Bat%2B4.12.01%2BPM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="420" data-original-width="1170" height="229" src="https://3.bp.blogspot.com/-RnBe7nKtP1E/XAWqd0szkZI/AAAAAAAAB7w/QbPO5-Qhh8AFr4JctSE97lWyPuN8GDcAgCLcBGAs/s640/Screen%2BShot%2B2018-12-03%2Bat%2B4.12.01%2BPM.png" width="640" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
As a serverless platform I used <b><a href="https://fnproject.io/">Fn Project</a></b>.<b> </b>The beauty of it is that it's a container-native serverless platform, totally based on Docker containers and it can be easily run locally on my laptop (what I did for this post) or somewhere in the cloud, let's say on <b><a href="https://cloud.oracle.com/containers/kubernetes-engine">Oracle Kubernetes Engine</a></b>.<br />
<br />
Ok, let's get into the implementation details from left to right of the diagram.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-FInXsR70s8c/XAWyInLT59I/AAAAAAAAB78/cKurHE0MxOkjjJsAU_Pn3rkwfr0QwPCzQCLcBGAs/s1600/Screen%2BShot%2B2018-12-03%2Bat%2B4.45.31%2BPM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="586" data-original-width="1600" height="233" src="https://2.bp.blogspot.com/-FInXsR70s8c/XAWyInLT59I/AAAAAAAAB78/cKurHE0MxOkjjJsAU_Pn3rkwfr0QwPCzQCLcBGAs/s640/Screen%2BShot%2B2018-12-03%2Bat%2B4.45.31%2BPM.png" width="640" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
So, the <b>listener </b>component, the ears, the one which recognizes my speech and converts it into text is implemented with <b>Python:</b><br />
<br />
The key code snippet of the component look like this (the full source code is available on <a href="https://github.com/eedorenko/flexdeploybot">GitHub</a>):<br />
<pre class="brush: python" name="code">r = sr.Recognizer()
mic = sr.Microphone()
with mic as source:
r.energy_threshold = 2000
while True:
try:
with mic as source:
audio = r.listen(source, phrase_time_limit=5)
transcript = r.recognize_google(audio)
print(transcript)
if active:
requests.post(url = URL, data = transcript)
time.sleep(5)
except sr.UnknownValueError:
print("Sorry, I don't understand you")</pre>
<br />
Why <b>Python</b>? There are plenty of available speech recognition libraries for Python, so you can play with them and choose the one which understands your accent better. I like Python.<br />
So, once the <b>listener </b>recognizes my speech it invokes an <b>Fn </b>function passing the phrase as a request body.<br />
The function <b>sendToBotFn</b> is implemented with Node.js:<br />
<pre class="brush: js" name="code">function buildSignatureHeader(buf, channelSecretKey) {
return 'sha256=' + buildSignature(buf, channelSecretKey);
}
function buildSignature(buf, channelSecretKey) {
const hmac = crypto.createHmac('sha256', Buffer.from(channelSecretKey, 'utf8'));
hmac.update(buf);
return hmac.digest('hex');
}
function performRequest(headers, data) {
var dataString = JSON.stringify(data);
var options = {
body: dataString,
headers: headers
};
request('POST', host+endpoint, options);
}
function sendMessage(message) {
let messagePayload = {
type: 'text',
text: message
}
let messageToBot = {
userId: userId,
messagePayload: messagePayload
}
let body = Buffer.from(JSON.stringify(messageToBot), 'utf8');
let headers = {};
headers['Content-Type'] = 'application/json; charset=utf-8';
headers['X-Hub-Signature'] = buildSignatureHeader(body, channelKey);
performRequest(headers, messageToBot);
}
fdk.handle(function(input){
sendMessage(input);
return input;
})
</pre>
Why Node.js? It's not because I like it. No. It's because Oracle documentation on implementing a <b><a href="https://docs.oracle.com/en/cloud/paas/autonomous-digital-assistant-cloud/use-chatbot/channels-topic.html#GUID-96CCA06D-0432-4F20-8CDD-E60161F46680">custom web hook channel</a> </b>is referring to Node.js. They like it.<br />
<br />
When the chatbot is responding it is invoking a webhook referring to an <b>Fn </b>function <b>receiveFromBotFn</b> running on my laptop.<b> </b>I use<b> <a href="https://ngrok.com/">ngrok tunnel</a> </b>to expose my <b>Fn </b>application listening to localhost:8080 to the Internet. The <b>receiveFromBotFn </b>function is also implemented with Node.js:<br />
<pre class="brush: js" name="code">const fdk=require('@fnproject/fdk');
const request = require('sync-request');
const url = 'http://localhost:4390';
fdk.handle(function(input){
var sayItCall = request('POST', url,{
body: input.messagePayload.text,
});
return input;
})
</pre>
The function sends an http request to a simple web server running locally and listening to 4390 port.<br />
I have to admit that it's really easy to implement stuff like that with Node.js. The web server uses Mac OS X native utility <b>say </b>to pronounce whatever comes in the request body:<br />
<pre class="brush: js" name="code">var http = require('http');
const exec = require("child_process").exec
const request = require('sync-request');
http.createServer(function (req, res) {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
exec('say '+body, (error, stdout, stderr) => {
});
res.end('ok');
});
res.end();
}).listen(4390);
</pre>
In order to actually invoke the back-end to create a snapshot with FlexDeploy the chatbot invokes with the <b>custombotcomponent</b> an <b>Fn </b>function <b>createSnapshotFn</b>:<br />
<pre class="brush: js" name="code">fdk.handle(function(input){
var res=request('POST',fd_url+'/flexdeploy/rest/v1/releases/'+input+'/snapshot', {
json: {action : 'createSnapshot'},
});
return JSON.parse(res.getBody('utf8'));
})
</pre>
<div>
The function is simple, it just invokes FlexDeploy REST API to start building a snapshot for the given release. It is also implemented with Node.js, however I am going to rewrite it with Java. I love Java. Furthermore, instead of a simple function I am going to implement an <b>Fn Flow </b>that first checks if the given release exists and if it is valid and only after that it invokes the <b>createSnapshotFn </b>function for that release. In the next post.<br />
<br />
<br />
That's it!</div>
<br />
<br /></div>
<br /></div>Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-25459827706049569582018-11-24T12:04:00.000-08:002019-04-28T12:42:44.599-07:00Persistent Volumes for Database Containers running on a K8s cluster in the Cloud<div dir="ltr" style="text-align: left;" trbidi="on">
In <a href="http://adfpractice-fedor.blogspot.com/2018/07/run-oracle-xe-docker-container-on.html">one of my previous posts</a> I showed how we can run <b>Oracle XE</b> database on a <b>K8s</b> cluster. That approach works fine for the use-cases when we don't care about the data and we are fine with loosing it when the container is redeployed and the pod is restarted. But if we want to keep the data, if we want it to survive all rescheduling we'll want to reconsider <b>K8s</b> resources used to run the DB container on the cluster. That said, the <b>yaml </b>file defining the resources looks like this one:<br />
<br />
<pre class="java" name="code">apiVersion: apps/v1beta2
kind: StatefulSet
metadata:
name: oraclexe
labels:
run: oraclexe
spec:
selector:
matchLabels:
run: oraclexe
serviceName: "oraclexe-svc"
replicas: 1
template:
metadata:
labels:
run: oraclexe
spec:
volumes:
- name: dshm
emptyDir:
medium: Memory
containers:
- image: eugeneflexagon/database:11.2.0.2-xe
volumeMounts:
- mountPath: /dev/shm
name: dshm
- mountPath: /u01/app/oracle/oradata
name: db
imagePullPolicy: Always
name: oraclexe
ports:
- containerPort: 1521
protocol: TCP
volumeClaimTemplates:
- metadata:
name: db
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 100M
---
apiVersion: v1
kind: Service
metadata:
name: oraclexe-svc
labels:
run: oraclexe
spec:
selector:
run: oraclexe
ports:
- port: 1521
targetPort: 1521
type: LoadBalancer</pre>
<br />
<div style="margin-bottom: 0pt; margin-top: 4pt; word-break: normal;">
There are some interesting things here. First of all this is not a deployment. We are defining here another <b>K8s</b> resource which is called <b>Stateful Set</b>. Unlike a <b>Deployment</b>, a <b>Stateful Set</b> maintains a sticky identity for each of their <b>Pods</b>. These pods are created from the same specification, but they are not interchangeable: each has a persistent identifier that it maintains across any rescheduling.<br />
<br />
This guy has been specially designed for stateful applications like database that save their data to a persistent storage. In order to define a persistent storage for our database we use a special <b>K8s</b> resource <b>Persistent Volume</b> and here in the <b>yaml</b> file we are defining a claim to create a 100mb <b>Persistent Volume</b> with name <b>db</b>. This volume provides read/write access mode for one assigned pod. The volume is called persistent because its lifespan is not maintained by a container and not even by a pod, it’s maintained by a <b>K8s</b> cluster. So it can outlive any containers and pods and save the data. We are referring to this persistence volume in the container definition mounting a volume on path <b>/u01/app/oracle/oradata. </b>This is where Oracle DB XE container stores its data.<br />
<br />
That's it!<br />
<br /></div>
</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-55983612531179826562018-10-31T15:08:00.001-07:002018-10-31T15:08:55.309-07:00Develop, Build, Deliver and Run Microservices with Containers in the Cloud<div dir="ltr" style="text-align: left;" trbidi="on">
<div class="post-body entry-content" style="color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13.199999809265137px; line-height: 1.4; position: relative; width: 630px;">
<div dir="ltr" trbidi="on">
<div style="color: #232323; font-family: Arial; font-size: 16px;">
In this post I would like to thank everyone, who managed to attend my sessions "<b>Develop, Build, Deliver and Run Microservices with Containers in the Cloud</b>" at <a href="https://www.oracle.com/code-one/index.html">Oracle Code One</a> and "<b>Develop, Deliver, Run Oracle ADF applications with Docker</b>" at <a href="https://www.oracle.com/openworld/index.html">Oracle Open World 2018</a>. Thank you guys for coming to listen to me, to learn something new and to ask a lot of interesting questions. </div>
<div style="color: #232323; font-family: Arial; font-size: 16px;">
<br /></div>
<div style="color: #232323; font-family: Arial; font-size: 16px;">
The presentations are available on the <a href="https://oracle.rainfocus.com/widget/oracle/oow18/catalogoow18?search=Fedorenko">content catalog</a> and on Slide Share as well: </div>
<div style="color: #888888; font-family: Arial; font-size: 16px;">
</div>
<iframe allowfullscreen="" frameborder="0" height="485" marginheight="0" marginwidth="0" scrolling="no" src="//www.slideshare.net/slideshow/embed_code/key/G1nkgIPzt2OE5F" style="border-width: 1px; border: 1px solid #ccc; margin-bottom: 5px; max-width: 100%;" width="595"> </iframe> <br />
<div style="color: #232323; font-family: Arial; font-size: 16px; min-height: 18px;">
<br /></div>
<iframe allowfullscreen="" frameborder="0" height="485" marginheight="0" marginwidth="0" scrolling="no" src="//www.slideshare.net/slideshow/embed_code/key/khiz3o1ETjc7HY" style="border-width: 1px; border: 1px solid #ccc; margin-bottom: 5px; max-width: 100%;" width="595"> </iframe> <br />
<div style="color: #232323; font-family: Arial; font-size: 16px; min-height: 18px;">
<br /></div>
<div style="color: #232323; font-family: Arial; font-size: 16px;">
Happy Halloween!</div>
</div>
</div>
</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-6941758140719745922018-09-29T11:27:00.000-07:002018-09-29T11:27:39.308-07:00Configuring a Datasource in a Docker Container<div dir="ltr" style="text-align: left;" trbidi="on">
In this post I am going to show how to configure a datasource consumed by an ADF application running on Tomcat in a Docker container.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-YVVQn0uPKOs/W6_Ajy_hLFI/AAAAAAAAB5w/N62ud3T_3ocHIFm76E2QaWCehtL0zKCNgCLcBGAs/s1600/Screen%2BShot%2B2018-09-29%2Bat%2B1.12.15%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="602" data-original-width="1590" height="151" src="https://4.bp.blogspot.com/-YVVQn0uPKOs/W6_Ajy_hLFI/AAAAAAAAB5w/N62ud3T_3ocHIFm76E2QaWCehtL0zKCNgCLcBGAs/s400/Screen%2BShot%2B2018-09-29%2Bat%2B1.12.15%2BPM.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
So, there is a Docker container <b>sample-adf </b>with a Tomcat application server preconfigured with ADF libraries and with an ADF application running on top of Tomcat. The ADF application requires a connection to an external database.<br />
The application is implemented with ADF BC and it's <b>application module </b>is referring to a datasource <b>jdbc/appDS</b>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-44cI7YUqCS8/W6-wdI3D7AI/AAAAAAAAB5k/C57-60zgNwkZ0lxlsnPS_N6hQLp4d4KGACLcBGAs/s1600/Screen%2BShot%2B2018-09-29%2Bat%2B12.03.30%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="610" data-original-width="1004" height="194" src="https://1.bp.blogspot.com/-44cI7YUqCS8/W6-wdI3D7AI/AAAAAAAAB5k/C57-60zgNwkZ0lxlsnPS_N6hQLp4d4KGACLcBGAs/s320/Screen%2BShot%2B2018-09-29%2Bat%2B12.03.30%2BPM.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
This datasource is configured inside a container in Tomcat <b>/conf/context.xml</b> file. The JDBC url, username and password are provided by environment variables:<br />
<br />
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"><Resource name="jdbc/appDS" auth="Container"</span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> type="oracle.jdbc.pool.OracleDataSource"</span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> factory="oracle.jdbc.pool.OracleDataSourceFactory"</span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> url="${DB_URL}"</span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> user="${DB_USERNAME}"</span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> password="${DB_PWD}"</span></div>
<br />
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> ...</span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"></span></div>
These variables are propagated to the application server in Tomcat /<b>bin/setenv.sh</b> file:<br />
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="-webkit-font-kerning: none; font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="-webkit-text-stroke-width: initial;">CATALINA_OPTS='-DDB_URL=$DB_URL </span>-DDB_USERNAME=$DB_USERNAME -DDB_PWD=DB_PWD ...<span style="-webkit-text-stroke-width: initial;">'</span></div>
<br />
Having these configurations set, we can <b>run</b> a container providing values of the variables:<br />
<br />
<span style="font-family: "verdana";"><b><span style="-webkit-text-stroke-width: initial;">docker run --name </span>adf -e DB_URL="jdbc:oracle:thin:@myhost:1521:xe" -e DB_USERNAME=system -e DB_PWD=welcome1 sample-adf</b></span><br />
<br />
If we are about to run a container in a <b>K8s</b> cluster we can provide variable values in a <b>yaml</b> file:<br />
<br />
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;">spec:</span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> containers:</span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> - image: </span>sample-adf</div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> env:</span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> - name: </span>DB_URL</div>
<span style="font-family: "verdana"; font-size: 16px;"> value: "</span><span style="font-family: "verdana"; font-size: small;">jdbc:oracle:thin:@myhost:1521:xe</span><span style="font-family: "verdana"; font-size: 16px;">"</span><br />
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="-webkit-font-kerning: none; font-variant-ligatures: no-common-ligatures;"> - name: </span>DB_USERNAME</div>
<span style="font-family: "verdana"; font-size: 16px;"> value: "</span><span style="font-family: "verdana"; font-size: small;">system</span><span style="font-family: "verdana"; font-size: 16px;">"</span><br />
<div>
<span style="font-family: verdana; font-size: 16px;"> - name: </span><span style="font-family: verdana; font-size: 16px;">DB_PWD</span><span style="-webkit-font-kerning: none; font-variant-ligatures: no-common-ligatures;"></span><br />
<span style="font-family: verdana; font-size: 16px;"> value: "</span><span style="font-family: verdana; font-size: small;">welcome1</span><span style="font-family: verdana; font-size: 16px;">"</span><span style="-webkit-font-kerning: none; font-variant-ligatures: no-common-ligatures;"></span><br />
<span style="-webkit-font-kerning: none; font-variant-ligatures: no-common-ligatures;">
</span>
<div>
<span style="-webkit-font-kerning: none; font-variant-ligatures: no-common-ligatures;"><span style="-webkit-font-kerning: none; font-variant-ligatures: no-common-ligatures;"><br /></span></span></div>
<span style="-webkit-font-kerning: none; font-variant-ligatures: no-common-ligatures;">
</span></div>
<br />
In order to make this <b>yaml </b>file portable we would avoid providing exact values and refer to K8s <b>ConfigMaps </b>and<b> Secrets </b>instead of that<b>. </b><br />
<br />
A <b>ConfigMap</b> is a named K8s resource that allows us to decouple configuration artifacts from image content to keep containerized applications portable. This is just a simple set of key-value paires. And obviously those values in each K8s cluster, in each environment are different.<br />
<br />
Similar approach is used when it comes to sensitive data like user names and passwords. Only in this case instead of configmaps we use a special resource which is called <b>Secret</b>. The data is encoded and it is only sent to a node if a pod on that node requires it. It is deleted once the pod that depends on it is deleted.<br />
<b><br /></b>We can create ConfigMaps and Secrets out of key-value files or just by providing the values in a command line:<br />
<br />
<b><span style="font-family: "verdana";">kubectl create configmap adf-config </span></b><br />
<b><span style="font-family: "verdana";">--from-literal=db.url="</span><span style="font-family: verdana;">jdbc:oracle:thin:@myhost:1521:xe</span><span style="font-family: verdana;">"</span></b><br />
<span style="font-family: verdana;"><b><br /></b></span>
<b><span style="font-family: "verdana";">kubectl create secret generic adf-secret </span></b><br />
<b><span style="font-family: "verdana";">--from-literal=db.username="</span><span style="font-family: verdana;">system</span><span style="font-family: verdana;">" </span></b><br />
<b><span style="font-family: "verdana";">--from-literal=db.pwd="welcome1</span><span style="font-family: verdana;">"</span></b><br />
<b><br /></b>
<b><br /></b>Having done that we can specify in the <b>yaml</b> file that values for the environment variables should be fetched from <b>adf-config</b> ConfigMap and <b>adf-secret</b> Secret:<br />
<br />
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;">spec:</span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> containers:</span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> - image: </span>sample-adf</div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> env:</span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> - name: </span>DB_URL<br />
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> valueFrom:</span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> configMapKeyRef:</span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-stretch: normal; line-height: normal;">
<span style="font-kerning: none; font-variant-ligatures: no-common-ligatures;"> name: adf-config</span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-stretch: normal; line-height: normal;">
<span style="-webkit-text-stroke-width: initial;"> key: db.url</span></div>
</div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="-webkit-font-kerning: none; font-variant-ligatures: no-common-ligatures;"> - name: </span>DB_USERNAME<br />
<span style="-webkit-text-stroke-color: rgb(0, 0, 0); font-family: Verdana;"> valueFrom:</span><br />
<span style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana;"> secretKeyRef:</span><br />
<div style="-webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; color: black; font-family: verdana; font-size: 16px; font-stretch: normal; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px;">
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-stretch: normal; line-height: normal; margin: 0px;">
<span style="-webkit-font-kerning: none; font-variant-ligatures: no-common-ligatures;"> name: adf-secret</span></div>
<div style="margin: 0px;">
</div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-stretch: normal; line-height: normal; margin: 0px;">
<span style="-webkit-font-kerning: none; font-variant-ligatures: no-common-ligatures;"> key: db.username</span></div>
</div>
</div>
<div>
<span style="font-family: verdana; font-size: 16px;"> - name: </span><span style="font-family: verdana; font-size: 16px;">DB_PWD</span><span style="-webkit-font-kerning: none; font-variant-ligatures: no-common-ligatures;"></span><br />
<div style="font-family: verdana; font-size: 16px; font-stretch: normal; line-height: normal;">
<span style="-webkit-text-stroke-color: rgb(0, 0, 0); font-family: Verdana;"> valueFrom:</span><br />
<span style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana;"> secretKeyRef:</span><br />
<div style="font-stretch: normal; line-height: normal;">
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-stretch: normal; line-height: normal;">
<span style="-webkit-font-kerning: none; font-variant-ligatures: no-common-ligatures;"> name: adf-secret</span></div>
<div style="-webkit-text-stroke-color: rgb(0, 0, 0); -webkit-text-stroke-width: initial; font-family: Verdana; font-stretch: normal; line-height: normal;">
<span style="-webkit-text-stroke-width: initial;"> key: db.pwd</span></div>
<div>
<span style="-webkit-font-kerning: none; font-variant-ligatures: no-common-ligatures;"><br /></span></div>
</div>
</div>
</div>
<br />
That's it!<br />
<div>
<br /></div>
</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-65649839933001798662018-08-31T08:11:00.000-07:002018-09-02T08:14:28.355-07:00Remote access to Minikube with Kubectl<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
Let's say you need to install a <b>Kubernetes</b> cluster in your organization for development and testing purposes. <a href="https://kubernetes.io/docs/setup/minikube/">Minikube</a> looks like a perfect fit for that job. It was specially designed for users looking to try out Kubernetes or develop with it day-to-day. It runs a single-node Kubernetes cluster inside a VM on a standalone machine. So, you found a server for that, followed the <a href="https://kubernetes.io/docs/setup/minikube/#installation">insulation guide</a> to install a virtual box with <b>Minikube</b> on it and now you can easily deploy pods to the K8s cluster with <b>kubectl</b> from that server. In order to be able to do the same remotely from your laptop you have to do some extra movements:<br />
<br />
1. <a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/">Install</a> <b>kubectl</b> on your laptop if you don't have it.<br />
2. Copy .<b>minikube </b>folder from the server with <b>Minikube </b>to your laptop (e.g. to /Users/fedor/work/minikube)<br />
3. Update <b>clusters</b>, <b>contexts</b> and <b>users </b>sections in your <b>kubectl </b>config file on your laptop ($HOME/.kube/config) with the following content<br />
<pre class="java" name="code">apiVersion: v1
clusters:
- cluster:
insecure-skip-tls-verify: true
server: https://YOUR_SERVER:51928
name: minikube
contexts:
- context:
cluster: minikube
user: minikube
name: minikube
current-context: minikube
kind: Config
preferences: {}
users:
- name: minikube
user:
client-certificate: /Users/fedor/work/minikube/client.crt
client-key: /Users/fedor/work/minikube/client.key
</pre>
4. Go to the server and stop <b>Minikube</b> with<br />
<pre class="java" name="code">minikube stop
</pre>
5. Forward a port for the <b>Minikube </b>VM from 8443 guest port to 51928 host port.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-ovG7luMFE18/W4v8s3aricI/AAAAAAAAB4g/wHVCCgxF7UIpsGkAQbvhn_dVjsqDZgaYgCLcBGAs/s1600/Screen%2BShot%2B2018-09-02%2Bat%2B10.06.57%2BAM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="855" data-original-width="1284" height="212" src="https://1.bp.blogspot.com/-ovG7luMFE18/W4v8s3aricI/AAAAAAAAB4g/wHVCCgxF7UIpsGkAQbvhn_dVjsqDZgaYgCLcBGAs/s320/Screen%2BShot%2B2018-09-02%2Bat%2B10.06.57%2BAM.png" width="320" /></a></div>
<br />
<b><br /></b>
6. Start <b>Minikube</b> with <b> </b></div>
<pre class="java" name="code">minikube start
</pre>
7. Check from your laptop that it works:<br />
<pre class="java" name="code">kubectl get pods</pre>
<br />
That's it!</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-35433561923180153822018-07-30T16:48:00.000-07:002018-07-30T16:55:01.856-07:00Run Oracle XE Docker Container on Amazon EKS<div dir="ltr" style="text-align: left;" trbidi="on">
Recently Amazon announced general availability of their new service <a href="http://email.awscloud.com/dc/KwqiTCOQ16Q1JCi3MdelDxGsAkfAWLp05mVvBjpzv_JuemymAxHKLmpzJmtXwCMBsw7bDhSRIxDeRoJI0S2mgPOMighf6fTc6w68Q1btioSZlvPTIJTwVTcOmt-9fnfF0kCy7f3Pu4j3wST1LjL5lsF5LM7ZoOQYbQWoS8QCXhP9rYMMdQlV79aieUKkDGHJhCrTd48Q6NSedKsUWSb2jca239A9C0MKOhZv6UmbVP5mqSj0IBTgVTkF2_0IwqXxGBLpcT8NIPJnu3KzY_VLFw==/ttMq0x79Tu0Oo07kZG000OR">Amazon Elastic Container Service for Kubernetes (Amazon EKS)</a>. This is a managed service to deploy, manage and scale containerized applications using K8s on AWS. I decided to get my hands dirty with it and deployed a Docker container with <b>Oracle XE Database</b> to a<b> K8s</b> cluster on <b>Amazon EKS</b>. In this post I am going to describe what I did to make that happen.<br />
<br />
<b>1.</b> Create Oracle XE Docker image.<br />
<br />
First of all we need a <b>Docker</b> image with <b>Oracle XE</b> database:<br />
<br />
<b>1.1</b> Clone Oracle GitHub repository to build docker images:<br />
<pre class="java" name="code">git clone https://github.com/oracle/docker-images.git oracle-docker-images
</pre>
It will create <b>oracle-docker-images</b> folder.<br />
<br />
<b>1.2</b> Download Oracle XE binaries from <a href="http://www.oracle.com/technetwork/database/database-technologies/express-edition/downloads/index.html">OTN</a><br />
<br />
<b>1.3</b> Copy the downloaded stuff to <b>../oracle-docker-images/OracleDatabase/SingleInstance/dockerfiles/11.2.0.2</b> folder<br />
<br />
<b>1.4</b> Build the Docker image<br />
<pre class="java" name="code">./buildDockerImage.sh -v 11.2.0.2 -x -I</pre>
<pre class="java" name="code"></pre>
<b>1.5</b> Check the new image
<br />
<pre class="java" name="code">docker images oracle/database:11.2.0.2-xe
</pre>
<b>1.6</b>. Rename the image so you can push it to Docker Hub. E.g.:<br />
<pre class="java" name="code">docker tag oracle/database:11.2.0.2-xe eugeneflexagon/database:11.2.0.2-xe
</pre>
<br />
Ok, so having done that, we have Oracle XE Docker image stored in Docker Hub repository.<br />
<br />
<b>2.</b> Create K8s cluster on Amazon EKS.<br />
<br />
Assuming that you have already AWS account, take your favorite tambourine (you will need it) and create a K8s cluster following this guide <a href="https://docs.aws.amazon.com/eks/latest/userguide/getting-started.html">Getting Started with Amazon EKS</a> (a good example of how complicated you can make a "getting started guide").<br />
<br />
Once you are able to see your working nodes in <b>Ready </b>status, you're good to move forward<br />
<pre class="java" name="code">kubectl get nodes --watch
</pre>
<b>3.</b> Configure Load Balancer<br />
<br />
In <a href="https://us-west-2.console.aws.amazon.com/ec2/v2/home?region=us-west-2#LoadBalancers:sort=loadBalancerName">AWS console</a> go to your <b>EC2 Dashboard </b>and look at the <b>Load Balancers </b>tab:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-_5qo9Iw8tn4/W1-I7SXSp1I/AAAAAAAAB2Y/-_Jh-zT4W3sE_Vq3ZaxHl8B8YurPuL5rQCLcBGAs/s1600/Screen%2BShot%2B2018-07-30%2Bat%2B4.53.23%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="874" data-original-width="780" height="320" src="https://4.bp.blogspot.com/-_5qo9Iw8tn4/W1-I7SXSp1I/AAAAAAAAB2Y/-_Jh-zT4W3sE_Vq3ZaxHl8B8YurPuL5rQCLcBGAs/s320/Screen%2BShot%2B2018-07-30%2Bat%2B4.53.23%2BPM.png" width="285" /></a></div>
<br />
<br />
<br />
Click on <b>Create Load Balancer, </b>select<b> Network Load Balancer:</b><br />
<b><br /></b>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-16gdYKmrN3Q/W1-JfSdwQCI/AAAAAAAAB2g/07NhrjFNo3Uy__LeIES4l7VXHDzJf8hAACLcBGAs/s1600/Screen%2BShot%2B2018-07-30%2Bat%2B4.55.11%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="902" data-original-width="1044" height="276" src="https://4.bp.blogspot.com/-16gdYKmrN3Q/W1-JfSdwQCI/AAAAAAAAB2g/07NhrjFNo3Uy__LeIES4l7VXHDzJf8hAACLcBGAs/s320/Screen%2BShot%2B2018-07-30%2Bat%2B4.55.11%2BPM.png" width="320" /></a></div>
<br />
change the listener port to <b>1521</b><br />
<b><br /></b>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-lrmgp87VuzY/W1-eSmRHIPI/AAAAAAAAB3c/NYZs5tHQG44WxQE6FuAW1L5rccYGh8PdgCLcBGAs/s1600/Screen%2BShot%2B2018-07-30%2Bat%2B6.24.02%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="351" data-original-width="1600" height="87" src="https://3.bp.blogspot.com/-lrmgp87VuzY/W1-eSmRHIPI/AAAAAAAAB3c/NYZs5tHQG44WxQE6FuAW1L5rccYGh8PdgCLcBGAs/s400/Screen%2BShot%2B2018-07-30%2Bat%2B6.24.02%2BPM.png" width="400" /></a></div>
<b><br /></b>
and specify in <b>Availability Zones </b>the VPC that you have just created for the K8s cluster:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-kF-o9wN1x8g/W1-J9HRRasI/AAAAAAAAB2o/m8kodo7psM4zSjjIo63xRcKOr0r6o2ObwCLcBGAs/s1600/Screen%2BShot%2B2018-07-30%2Bat%2B4.56.41%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="722" data-original-width="1100" height="210" src="https://2.bp.blogspot.com/-kF-o9wN1x8g/W1-J9HRRasI/AAAAAAAAB2o/m8kodo7psM4zSjjIo63xRcKOr0r6o2ObwCLcBGAs/s320/Screen%2BShot%2B2018-07-30%2Bat%2B4.56.41%2BPM.png" width="320" /></a></div>
The scheme should be <b>internet-facing.</b><br />
<b><br />
4.</b> Deploy Oracle XE Docker container to the K8s cluster.<br />
<br />
<b>4.1</b> Create a <b>yaml </b>file with the following content:<br />
<pre class="java" name="code">apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: oraclexe
labels:
run: oraclexe
spec:
replicas: 1
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
labels:
run: oraclexe
spec:
volumes:
- name: dshm
emptyDir:
medium: Memory
containers:
- image: eugeneflexagon/database:11.2.0.2-xe
volumeMounts:
- mountPath: /dev/shm
name: dshm
imagePullPolicy: Always
name: oraclexe
ports:
- containerPort: 1521
protocol: TCP
imagePullSecrets:
- name: wrelease
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: oraclexe-svc
spec:
selector:
run: oraclexe
ports:
- port: 1521
targetPort: 1521
type: LoadBalancer
</pre>
<b>4.2 </b>Deploy it:<br />
<pre class="java" name="code">kubectl apply -f oraclexe-deployment.yaml
</pre>
<br />
<b>5.</b> Check how it works
<br />
<br />
<b>5.1.</b> Get a list of pods and check the logs:<br />
<pre class="java" name="code">kubectl get pods
kubectl logs -f POD_NAME
</pre>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-MNxP0jUEDn4/W1-byWTW8PI/AAAAAAAAB20/c0qIq5M5qtcISUSBtDZXIlUHRfNXfNCKACLcBGAs/s1600/Screen%2BShot%2B2018-07-30%2Bat%2B6.13.47%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="219" data-original-width="698" height="125" src="https://1.bp.blogspot.com/-MNxP0jUEDn4/W1-byWTW8PI/AAAAAAAAB20/c0qIq5M5qtcISUSBtDZXIlUHRfNXfNCKACLcBGAs/s400/Screen%2BShot%2B2018-07-30%2Bat%2B6.13.47%2BPM.png" width="400" /></a></div>
<pre class="java" name="code"></pre>
<pre class="java" name="code"></pre>
Once you see in the logs <b>DATABASE IS READY TO USE! </b>the database container is up and running.<br />
<br />
Note, that the container while starting generated a password for <b>sys</b> and <b>system </b>users. You can find this password in the log:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-Or5Vq3YNYJY/W1-c4oTuBAI/AAAAAAAAB3A/XNVUY1I_brEYwENTWjAifvDh25AND-BWgCLcBGAs/s1600/Screen%2BShot%2B2018-07-30%2Bat%2B6.18.38%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="21" data-original-width="378" height="17" src="https://2.bp.blogspot.com/-Or5Vq3YNYJY/W1-c4oTuBAI/AAAAAAAAB3A/XNVUY1I_brEYwENTWjAifvDh25AND-BWgCLcBGAs/s320/Screen%2BShot%2B2018-07-30%2Bat%2B6.18.38%2BPM.png" width="320" /></a></div>
<br />
<br />
<b>5.2</b> Get external IP address of the service:<br />
<pre class="java" name="code">kubectl get svc
</pre>
Wait until the address in <b>EXTERNAL-IP</b> column turns from <b>PENDING</b> into something meaningful:<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-OkXW5pnQHDM/W1-dENC0aEI/AAAAAAAAB3E/kkuAcmZqvTUyN0PMMwb-9OWeJkL4ZO3TQCLcBGAs/s1600/Screen%2BShot%2B2018-07-30%2Bat%2B6.19.21%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="91" data-original-width="1600" height="22" src="https://4.bp.blogspot.com/-OkXW5pnQHDM/W1-dENC0aEI/AAAAAAAAB3E/kkuAcmZqvTUyN0PMMwb-9OWeJkL4ZO3TQCLcBGAs/s400/Screen%2BShot%2B2018-07-30%2Bat%2B6.19.21%2BPM.png" width="400" /></a></div>
<br />
<b>5.3</b> Connect to the DB:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-ulRR-qQQwAA/W1-dwsNnP8I/AAAAAAAAB3U/tWTTo6lHnVIW9RCXSobCb0yvYlOtFdKAgCLcBGAs/s1600/Screen%2BShot%2B2018-07-30%2Bat%2B6.22.20%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1026" data-original-width="1600" height="205" src="https://1.bp.blogspot.com/-ulRR-qQQwAA/W1-dwsNnP8I/AAAAAAAAB3U/tWTTo6lHnVIW9RCXSobCb0yvYlOtFdKAgCLcBGAs/s320/Screen%2BShot%2B2018-07-30%2Bat%2B6.22.20%2BPM.png" width="320" /></a></div>
<br />
<div>
That's it!</div>
</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-38625022475959597692018-06-29T15:52:00.000-07:002018-06-29T15:52:16.597-07:00Oracle Jet vs Oracle ADF or Oracle Jet with Oracle ADF<div dir="ltr" style="text-align: left;" trbidi="on">
<div style="color: #232323; font-family: Arial; font-size: 16px;">
In this post I would like to thank everyone, who managed to attend my session "Oracle Jet vs Oracle ADF or Oracle Jet with Oracle ADF" at ODTUG <a href="https://kscope18.odtug.com/">KScope18</a> conference in Orlando FL. Thank you guys for coming to listen to me, to learn something new and to ask a lot of interesting questions. </div>
<div style="color: #232323; font-family: Arial; font-size: 16px;">
<span style="font-size: 13px;"><br /></span>I promised at the session that the presentation would be available for download. </div>
<div style="color: #232323; font-family: Arial; font-size: 16px;">
The presentation is available here:</div>
<div style="color: #888888; font-family: Arial; font-size: 16px;">
<a href="https://www.slideshare.net/EugeneFedorenko4/jetvsadf" style="color: #888888; text-decoration: none;">https://www.slideshare.net/EugeneFedorenko4/jetvsadf</a></div>
<div style="color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13.199999809265137px;">
<br /></div>
<div style="color: #232323; font-family: Arial; font-size: 16px; min-height: 18px;">
<br /></div>
<div style="color: #232323; font-family: Arial; font-size: 16px;">
That's it!</div>
</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-61642534863842262512018-05-28T15:38:00.000-07:002018-05-29T15:24:56.287-07:00Oracle ADF and Oracle Jet work together. Architecture patterns. <div dir="ltr" style="text-align: left;" trbidi="on">
<div>
In this post I am going to consider various architecture patterns of implementing an application on top of combination of Oracle ADF and Oracle Jet. An organization practicing ADF may think on incorporating Oracle Jet for existing projects to refresh look&feel and make it modern and responsive and to implement new features in a new way. It may think on using Oracle Jet for totally new projects and obviously for projects related to development of hybrid applications for mobile devices.</div>
<div>
Oracle Jet is all about UI it is only about the client side part. So, the server side has to be implemented with something anyway. Obviously that many organizations will decide to use ADF for that in order to reuse their knowledge, experience, implementations and investments in ADF. It makes perfect sense. So, let's have a look at what options we have when it comes to combining Oracle Jet with Oracle ADF.</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
The first, most obvious and most popular option is to put Oracle Jet on top of ADF BC. So the client side for a web or for a hybrid mobile application is implemented with Jet and the serverside is ADF BC exposed as a Rest service. With JDeveloper 12.2.x you can expose ADF BC as Rest services in a few mouse clicks just like that.</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-JwO08IsCZzo/WwyFYz1OFZI/AAAAAAAABzs/YpiRrT3Ux4gwsD0jniI0GAJW4R5YeSRoQCEwYBhgL/s1600/Screen%2BShot%2B2018-05-28%2Bat%2B5.40.03%2BPM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="785" data-original-width="1600" height="312" src="https://3.bp.blogspot.com/-JwO08IsCZzo/WwyFYz1OFZI/AAAAAAAABzs/YpiRrT3Ux4gwsD0jniI0GAJW4R5YeSRoQCEwYBhgL/s640/Screen%2BShot%2B2018-05-28%2Bat%2B5.40.03%2BPM.png" width="640" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
The advantage of this approach is a pretty simple architecture. And what is simple has a chance to work longer. Another very valuable benefit is that we are reusing our resources, our knowledge and ADF experience, and if our existing ADF application is implemented right then we are going to reuse the most critical part of business logic implementation.</div>
<div>
However, we have to understand that ADF BC Business services working perfectly in an ADF application might be useless for a Jet applications. Why is that? The main reason is that we have changed the state management model. We switched from the classic ADF stateful behavior to the REST stateless model. Furthermore, more likely the UI design will be different in Jet Web and Hybrid applications. </div>
<div>
So, we need to create new ADF BC services supporting a stateless model and serving for the convenience of the new UI.</div>
<div>
<br /></div>
<div>
The good news is that we don’t have to build everything form scratch. If existing ADF BC model is built in the right way, we can reuse the core part of it including entities and business logic implemented at the entity level. </div>
<div>
So,we can split the entire ADF BC model into the core part containing entities, utilities, and shared AMs and the facade part containing specific AMs and VOs and providing services for an ADF application and for a Jet application. </div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-WUdOuZDCx8w/WwyFsQAk4UI/AAAAAAAABz0/gLNSDnDgpTgxLOoAaUbO-CXEmQ50nh6EwCEwYBhgL/s1600/Screen%2BShot%2B2018-05-28%2Bat%2B5.41.33%2BPM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="880" data-original-width="1244" height="452" src="https://4.bp.blogspot.com/-WUdOuZDCx8w/WwyFsQAk4UI/AAAAAAAABz0/gLNSDnDgpTgxLOoAaUbO-CXEmQ50nh6EwCEwYBhgL/s640/Screen%2BShot%2B2018-05-28%2Bat%2B5.41.33%2BPM.png" width="640" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
Having reconsidered our ADF BC and getting them ready to serve both ADF and Jet applications we can incorporate now Jet functionality into existing ADF projects. A common architecture approach is </div>
<div>
to implement some pages of the system with ADF, some web pages are implemented with Jet and there is also a mobile hybrid application which is also implemented with Oracle Jet.</div>
<div>
<img border="0" data-original-height="881" data-original-width="1336" height="420" src="https://2.bp.blogspot.com/-K2dJ0v47dxs/WwyF-QsJytI/AAAAAAAAB0A/HHpCI3hHKFQeQiSorpU9-811g6pAD5yRgCEwYBhgL/s640/Screen%2BShot%2B2018-05-28%2Bat%2B5.42.44%2BPM.png" width="640" /></div>
<div>
<br /></div>
<div>
The advantage of this approach is that we keep things separately. It looks like different applications working on top of a common business model. And each application introduces its own UI, suitable for those use-cases they are implemented for. Furthermore they provide different entry points to the entire system. We can access it through a regular ADF page, we can go with a mobile device or we can access it from a Jet web page which in its turn can be easily integrated into any parent web page, for example a portal application.</div>
<div>
But this advantage may turn into a disadvantage as for each entry point we should think about authentication, internalization, localization, etc.</div>
<div>
This approach brings more running pieces into the entire system structure, so CI, CD, automated testing, the environment become more complicated here.</div>
<div>
<br /></div>
<div>
Another obvious option would be to integrate Jet content into an ADF page, so that from the user perspective it looks like a single page but behind the scene this is a mix of two different web applications.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-ZerI0P1Gp0o/WwyGNK7QxbI/AAAAAAAAB0E/3kuJHlCpvCYbu_aOXAX3dujaYjG6D_9qQCEwYBhgL/s1600/Screen%2BShot%2B2018-05-28%2Bat%2B5.43.39%2BPM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="906" data-original-width="1308" height="442" src="https://1.bp.blogspot.com/-ZerI0P1Gp0o/WwyGNK7QxbI/AAAAAAAAB0E/3kuJHlCpvCYbu_aOXAX3dujaYjG6D_9qQCEwYBhgL/s640/Screen%2BShot%2B2018-05-28%2Bat%2B5.43.39%2BPM.png" width="640" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
This option is not my favorite, I would avoid it. Because, basically, what you are doing here is mixing two web applications on the same page. It means there will be two different sessions with separate transactions and therefore separate entity caches and user contexts. </div>
<div>
Jet content is not participating in JSF lifecycle so the entire page is being submitted in two different ways. ADF prefers to own the entire page so such nice features like responsive geometry management and Drag&Drop just won’t work with the Jet content.</div>
<div>
In my opinion this approach makes sense in very specific scenarios when we need to show on our page some content form outside. For example if our page is kind of a portal or a dashboard gathering in one place data from different sources. In this case the same Jet component can be used on a page like that and in a regular Jet application.</div>
<div>
<br /></div>
<div>
Same considerations are about the opposite approach when we integrate ADF content into a Jet page by the means of a remote task flow call. This technique makes sense but it should be used only in specific use cases when we want to reuse existing ADF functionality which is not implemented in Jet. At least not at this point in time. This approach should not be used as a standard instrument to build our application.<a href="https://2.bp.blogspot.com/-mp7N1fz2Pyg/WwyGeXeJr3I/AAAAAAAAB0M/PCPkWD0l6ike1UXSV8Hs4YODBaz8aH-TwCEwYBhgL/s1600/Screen%2BShot%2B2018-05-28%2Bat%2B5.44.42%2BPM.png" imageanchor="1" style="clear: left; display: inline !important; margin-bottom: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="914" data-original-width="1312" height="444" src="https://2.bp.blogspot.com/-mp7N1fz2Pyg/WwyGeXeJr3I/AAAAAAAAB0M/PCPkWD0l6ike1UXSV8Hs4YODBaz8aH-TwCEwYBhgL/s640/Screen%2BShot%2B2018-05-28%2Bat%2B5.44.42%2BPM.png" width="640" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
At the bottom line Oracle ADF and Oracle JET can work perfectly together and this is a good option for organizations having solid ADF background. The only thing is to choose wisely the architecture approach of combining these two completely different tools.</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
That's it!</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-8492389258582259972018-04-28T09:30:00.000-07:002018-04-28T09:38:44.640-07:00Building Oracle Jet applications with Docker Hub<div dir="ltr" style="text-align: left;" trbidi="on">
<div style="background-color: white; font-stretch: normal; line-height: normal; text-align: left;">
<div style="text-align: left;">
<span style="font-family: inherit;"><span style="font-variant-ligatures: no-common-ligatures;">In this post I am going to show a simple CI solution for an Oracle Jet application basing on Docker Hub Automated Builds feature. The solution is container native meaning that Docker Hub is going to automatically build a Docker image according to a Docker file. The image is going to be stored in Docker Hub registry. A Docker file is a set of instructions on how to build a Docker image and those instructions may contain any actions including building an Oracle Jet application. So, what we need to do is to create a proper Docker file and set up </span>Docker Hub Automated Build.<br />I am going to build an Oracle Jet application with OJet CLI, so I have created a Docker image having OJet CLI installed and serving as an actual builder. The image is built with the following Dockerfile:</span></div>
</div>
<div style="background-color: white; font-stretch: normal; line-height: normal;">
<br /></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; font-stretch: normal; line-height: normal;">
<pre class="java" name="code">FROM node</pre>
<pre class="java" name="code"><span style="font-family: menlo;">RUN npm install -g @oracle/ojet-cli</span></pre>
</div>
<div>
<span style="background-color: white;"><br /></span>
<span style="background-color: white;">By running this command:</span></div>
<div style="background-color: white; font-stretch: normal; line-height: normal;">
<span style="font-family: inherit;">
</span>
<pre class="java" name="code"><span style="font-family: inherit;">docker built -t eugeneflexagon/ojetbuilder .
</span></pre>
<span style="font-family: inherit;">
</span><br />
<span style="font-family: inherit;">Having done that we can use this builder image in a Dockerfile to build our Jet application:</span><br />
<pre class="java" name="code"><span style="font-family: "menlo"; font-size: 11px;"># Create an image from a "builder" Docker image
FROM eugeneflexagon/ojetbuilder
# Copy all sources inside the new image
COPY . .
# Build the appliaction. As a result this will produce web folder.
RUN ojet build
# Create another Docker image which runs Jet application
# It contains Nginx on top of Alpine and our Jet appliction (web folder)
# This image is the result of the build and it is going to be stored in Docker Hub
FROM nginx:1.10.2-alpine
COPY --from=0 web /usr/share/nginx/html
EXPOSE 80
</span><span style="font-family: inherit;">
</span></pre>
<span style="font-family: inherit;">Here we are using the </span><a href="https://docs.docker.com/develop/develop-images/multistage-build/" style="font-family: inherit;">multi-stage build</a><span style="font-family: inherit;"> Docker feature when we actually create two Docker images: one for building and one for running, and only the last one is going to be saved as the final image. So, I added this Docker file to my </span><a href="https://github.com/eedorenko/ojetdevops" style="font-family: inherit;">source code</a><span style="font-family: inherit;"> on GitHub.</span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">The next step is to configure Docker Hub Automated Build:</span><br />
<div style="font-family: menlo; font-size: 11px;">
<br /></div>
<div class="separator" style="clear: both; font-family: menlo; font-size: 11px; text-align: center;">
<a href="https://1.bp.blogspot.com/-_UuWfCGeb54/WuSXAWW471I/AAAAAAAAByM/KznWnRqDRpA7svnBvniPzrAyYI5snHEqQCLcBGAs/s1600/Screen%2BShot%2B2018-04-28%2Bat%2B10.43.01%2BAM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="500" data-original-width="1108" height="144" src="https://1.bp.blogspot.com/-_UuWfCGeb54/WuSXAWW471I/AAAAAAAAByM/KznWnRqDRpA7svnBvniPzrAyYI5snHEqQCLcBGAs/s320/Screen%2BShot%2B2018-04-28%2Bat%2B10.43.01%2BAM.png" width="320" /></a></div>
<div style="font-family: menlo; font-size: 11px;">
<br /></div>
<div style="font-family: menlo; font-size: 11px;">
<br /></div>
<div style="font-family: menlo; font-size: 11px;">
<br /></div>
<div class="separator" style="clear: both; font-family: menlo; font-size: 11px; text-align: center;">
<a href="https://4.bp.blogspot.com/-w8EpxV3Lnw0/WuSXE6YYq4I/AAAAAAAAByQ/xrLdrKCxlM0lgaFkC4svhCN3CDb05-vTwCLcBGAs/s1600/Screen%2BShot%2B2018-04-28%2Bat%2B10.43.16%2BAM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="615" data-original-width="1600" height="123" src="https://4.bp.blogspot.com/-w8EpxV3Lnw0/WuSXE6YYq4I/AAAAAAAAByQ/xrLdrKCxlM0lgaFkC4svhCN3CDb05-vTwCLcBGAs/s320/Screen%2BShot%2B2018-04-28%2Bat%2B10.43.16%2BAM.png" width="320" /></a></div>
<div style="font-family: menlo; font-size: 11px;">
<br /></div>
<div style="font-family: menlo; font-size: 11px;">
<br /></div>
<div class="separator" style="clear: both; font-family: menlo; font-size: 11px; text-align: center;">
<a href="https://3.bp.blogspot.com/-E17ro8vk0kM/WuSXJX2oVwI/AAAAAAAAByU/I0_B__OInnopwoK-Fhul6K-9T6deoim5ACLcBGAs/s1600/Screen%2BShot%2B2018-04-28%2Bat%2B10.44.01%2BAM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="796" data-original-width="1600" height="159" src="https://3.bp.blogspot.com/-E17ro8vk0kM/WuSXJX2oVwI/AAAAAAAAByU/I0_B__OInnopwoK-Fhul6K-9T6deoim5ACLcBGAs/s320/Screen%2BShot%2B2018-04-28%2Bat%2B10.44.01%2BAM.png" width="320" /></a></div>
<div style="font-family: menlo; font-size: 11px;">
<br /></div>
<div style="font-family: menlo; font-size: 11px;">
<br /></div>
</div>
<div style="background-color: white; font-stretch: normal; line-height: normal;">
<span style="font-family: "menlo"; font-size: 11px;"><br /></span>
<span style="font-family: inherit; font-variant-ligatures: no-common-ligatures;">That was easy. Now we can change the source code and once it is pushed to GutHub the build is automatically queued:</span><br />
<div style="font-family: menlo; font-size: 11px;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span>
</div>
<div class="separator" style="clear: both; font-family: menlo; font-size: 11px; text-align: center;">
<a href="https://2.bp.blogspot.com/-1r8Q47hJwCg/WuSZJWyp-gI/AAAAAAAAByo/mP68IO3ZnusJffImMmb1L_YVRGycuiFlgCLcBGAs/s1600/Screen%2BShot%2B2018-04-28%2Bat%2B10.53.18%2BAM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="779" data-original-width="1600" height="155" src="https://2.bp.blogspot.com/-1r8Q47hJwCg/WuSZJWyp-gI/AAAAAAAAByo/mP68IO3ZnusJffImMmb1L_YVRGycuiFlgCLcBGAs/s320/Screen%2BShot%2B2018-04-28%2Bat%2B10.53.18%2BAM.png" width="320" /></a></div>
<span style="font-family: "menlo"; font-size: 11px;"><br /></span>
<span style="font-family: inherit;"><span style="font-variant-ligatures: no-common-ligatures;"><br /></span>
<span style="font-variant-ligatures: no-common-ligatures;">Once the build is finished we can pull and run the container locally:</span></span><br />
<span style="font-family: inherit;"><span style="font-variant-ligatures: no-common-ligatures;"><br /></span></span>
<span style="font-family: inherit;">docker run -it -p 8082:80 eugeneflexagon/ojetdevops:latest</span><br />
<div style="font-stretch: normal; line-height: normal;">
<span style="font-family: inherit; font-variant-ligatures: no-common-ligatures;">
</span></div>
<div style="font-stretch: normal; line-height: normal;">
<span style="font-family: inherit; font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="font-stretch: normal; line-height: normal;">
<span style="font-family: inherit;">And see the result at <a href="http://localhost:8082/">http://localhost:8082</a></span></div>
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">That's it!</span></div>
</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-55035210748641444942018-04-18T08:36:00.001-07:002018-04-18T08:42:42.009-07:00Containers, Serverless and Functions in a nutshell<div dir="ltr" style="text-align: left;" trbidi="on">
<div style="color: #232323; font-family: Arial; font-size: 16px;">
In this post I would like to thank everyone, who managed to attend my session "Containers, Serverless and Functions in a nutshell" at <a href="https://developer.oracle.com/code/boston-april-2018">Oracle Code</a> conference in Boston. Thank you guys for coming to listen to me, to learn something new and to ask a lot of interesting questions. </div>
<div style="color: #232323; font-family: Arial; font-size: 16px;">
<span style="font-size: 13px;"><br /></span>I promised at the session that the presentation would be available for download. </div>
<div style="color: #232323; font-family: Arial; font-size: 16px;">
The presentation is available here:</div>
<div style="color: #888888; font-family: Arial; font-size: 16px;">
<a href="https://www.slideshare.net/EugeneFedorenko4/containers-serverless-and-functions-in-a-nutshell">https://www.slideshare.net/EugeneFedorenko4/containers-serverless-and-functions-in-a-nutshell</a></div>
<div>
<br /></div>
<div style="color: #232323; font-family: Arial; font-size: 16px; min-height: 18px;">
<br /></div>
<div style="color: #232323; font-family: Arial; font-size: 16px;">
That's it!</div>
</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-57464879362983719802018-03-31T17:17:00.000-07:002018-03-31T17:34:17.261-07:00Deploying to K8s cluster with Fn Function<div dir="ltr" style="text-align: left;" trbidi="on">
An essential step of any CI/CD pipeline is deployment. If the pipeline operates with Docker containers and deploys to <b>K8s</b> clusters then the goal of the deployment step is to deploy a specific Docker image (stored on some container registry) to a specific K8s cluster. Let's say there is a VM where this deployment step is being performed. There are a couple of things to be done with that VM before it can be used as a deploying-to-kuberenetes machine:<br />
<ul style="text-align: left;">
<li>install <b>kubectl</b> (K8s CLI) </li>
<li>configure access to K8s clusters where we are going to deploy </li>
</ul>
Having the VM configured, the deployment step does the following:
<br />
<pre class="java" name="code"># kubeconfig file contains access configuration to all K8s clusters we need
# each configuration is called "context"
export KUBECONFIG=kubeconfig
# switch to "google-cloud-k8s-dev" context (K8s cluster on Google Cloud for Dev)
# so all subsequent kubectl commands are applied to that K8s cluster
kubectl config use-context google-cloud-k8s-dev
# actually deploy by applying k8s-deployment.yaml file
# containing instructions on what image should be deployed and how
kubectl apply -f k8s-deployment.yaml</pre>
<br />
In this post I am going to show how we can create a preconfigured Docker container capable of deploying a Docker image to a K8s cluster. So, basically, it is going to work as a function with two parameters: docker image, K8s context. Therefore we are going to create a function in <b>Fn Project </b>basing on this "deployer" container and deploy to K8s just by invoking the function over http.<br />
<br />
The deployer container is going to be built from a <b>Dockerfile</b> with the following content:<br />
<pre class="java" name="code">FROM ubuntu
# install kubectl
ADD https://storage.googleapis.com/kubernetes-release/release/v1.6.4/bin/linux/amd64/kubectl /usr/local/bin/kubectl
ENV HOME=/config
RUN chmod +x /usr/local/bin/kubectl
RUN export PATH=$PATH:/usr/local/bin
# install rpl
RUN apt-get update
RUN apt-get install rpl -y
# copy into container k8s configuration file with access to all K8s clusters
COPY kubeconfig kubeconfig
# copy into container yaml file template with IMAGE_NAME placeholder
# and an instruction on how to deploy the container to K8s cluster
COPY k8s-deployment.yaml k8s-deployment.yaml
# copy into container a shell script performing the deployment
COPY deploy.sh /usr/local/bin/deploy.sh
RUN chmod +x /usr/local/bin/deploy.sh
ENTRYPOINT ["xargs","/usr/local/bin/deploy.sh"]
</pre>
It is worth looking at the k8s-deployment.yaml file. It contains <b>IMAGE_NAME</b> placeholder which is going to be replaced with the exact Docker image name while deployment:<br />
<br />
<pre class="java" name="code">apiVersion: extensions/v1beta1
kind: Deployment
...
spec:
containers:
- image: IMAGE_NAME
imagePullPolicy: Always
...
</pre>
The deploy.sh script which is being invoked once the container is started has the following content:<br />
<pre class="java" name="code">#!/bin/bash
# replace IMAGE_NAME placeholder in yaml file with the first shell parameter </pre>
<pre class="java" name="code">rpl IMAGE_NAME $1 k8s-deployment.yaml
export KUBECONFIG=kubeconfig
# switch to K8s context specified in the second shell parameter
kubectl config use-context $2
# deploy to K8s cluster
kubectl apply -f k8s-deployment.yaml
</pre>
So, we are going to build a docker image from the Dockerfile by invoking this docker command:<br />
<pre class="java" name="code">docker build -t efedorenko/k8sdeployer:1.0 .</pre>
Assuming there is Fn Project up and running somewhere (e.g. on K8s cluster as it is described in this <a href="http://adfpractice-fedor.blogspot.com/2018/03/run-fn-functions-on-k8s-on-google-cloud.html">post</a>) we can create an Fn application:<br />
<pre class="java" name="code">fn apps create k8sdeployerapp</pre>
Then create a route to the k8sdeployer container:<br />
<pre class="java" name="code">fn routes create k8sdeployerapp /deploy efedorenko/k8sdeployer:1.0</pre>
We have created a <b>function</b> deploying a Docker image to a K8s cluster. This function can be invoked over http like this:<br />
<pre class="java" name="code">curl http://35.225.120.28:80/r/k8sdeployer -d "google-cloud-k8s-dev efedorenko/happyeaster:latest"</pre>
This call will deploy efedorenko/happyeaster:latest Docker image to a K8s cluster on Google Cloud Platform.<br />
<br />
<br />
That's it!<br />
<br />
<br />
<br /></div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-62427287518931084982018-03-24T10:20:00.000-07:002019-02-22T12:32:52.331-08:00Run Fn Functions on K8s on Google Cloud Platform<div dir="ltr" style="text-align: left;" trbidi="on">
Recently, I have been playing a lot with Functions and Project Fn. Eventually, I got to the point where I had to go beyond a playground on my laptop and go to the real wild world. An idea of running Fn on a K8s cluster seemed very attractive to me and I decided to do that somewhere on prem or in the cloud. After doing some research on how to install and configure K8s cluster on your own on a bare metal I came to a conclusion that I was too lazy for that. So, I went (flew) to the cloud.<br />
<br />
In this post I am going to show how to run Fn on Kubernetes cluster hosted on the Google Cloud Platform. Why Google? There are plenty of other cloud providers with the K8s services.<br />
The thing is that Google <b>really </b>has Kubernetes cluster in the cloud which is available for everyone. They give you the service right away without asking to apply for a preview mode access (aka we'll reach out to you once we find you good enough for that), explaining why you need it, checking your background, credit history, etc. So, Google.<br />
<br />
Once you got through all formalities and finally have access to the <a href="https://cloud.google.com/kubernetes-engine/">Google Kubernetes Engine</a>, go to the <a href="https://cloud.google.com/sdk/docs/quickstarts">Quickstarts</a> page and follow the instructions to install Google Cloud SDK.<br />
<br />
If you don't have <b>kubectl </b>installed on your machine you can install it with <b>gcloud:</b><br />
<pre class="java" name="code">gcloud components install kubectl
</pre>
<br />
Follow the instructions on <a href="https://cloud.google.com/kubernetes-engine/docs/quickstart">Kubernetes Engine Quickstart</a> to configure <b>gcloud</b> and create a K8s cluster by invoking the following commands:<br />
<pre class="java" n="" name="code">gcloud container clusters create fncluster
gcloud container clusters get-credentials fncluster</pre>
Check the result with <b>kubectl</b>:<br />
<pre class="java" name="code">kubectl cluster-info</pre>
This will give you a list of K8s services in your cluster and their URLs.<br />
<br />
Ok, so this is our starting point. We have a new K8s cluster in the cloud on one hand and Fn project on another hand. Let's get them married.<br />
<br />
We need to install a tool managing Kubernetes packages (charts). Something similar to apt/yum/dnf/pkg on Linux. The tool is <a href="https://github.com/kubernetes/helm#install">Helm</a>. Since I am a happy Mac user I just did that:<br />
<pre class="java" name="code">brew install kubernetes-helm
</pre>
<br />
The rest of Helm installation options are available <a href="https://docs.helm.sh/using_helm/#installing-helm">here</a>.<br />
<br />
The next step is to install Tiller in the K8s cluster. This is a server part of Helm:<br />
<pre class="java" name="code">kubectl create serviceaccount --namespace kube-system tiller
kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
helm init --service-account tiller --upgrade</pre>
<br />
If you don't have Fn installed locally, you will want to install it so you have Fn CLI on your machine (on Mac or Linux): <br />
<code class="bash plain" style="-webkit-box-shadow: none !important; background-color: white; background-image: none !important; border-bottom-left-radius: 0px !important; border-bottom-right-radius: 0px !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border: 0px !important; bottom: auto !important; box-shadow: none !important; box-sizing: content-box !important; direction: ltr !important; display: inline !important; float: none !important; font-family: Monaco, Consolas, "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; font-size: 12px; height: auto !important; left: auto !important; line-height: 1.1em !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important;"><br /></code>
<code class="bash plain" style="-webkit-box-shadow: none !important; background-color: white; background-image: none !important; border-bottom-left-radius: 0px !important; border-bottom-right-radius: 0px !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border: 0px !important; bottom: auto !important; box-shadow: none !important; box-sizing: content-box !important; direction: ltr !important; display: inline !important; float: none !important; font-family: Monaco, Consolas, "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; font-size: 12px; height: auto !important; left: auto !important; line-height: 1.1em !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important;">curl -LSs https:</code><code class="bash plain" style="-webkit-box-shadow: none !important; background-color: white; background-image: none !important; border-bottom-left-radius: 0px !important; border-bottom-right-radius: 0px !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border: 0px !important; bottom: auto !important; box-shadow: none !important; box-sizing: content-box !important; direction: ltr !important; display: inline !important; float: none !important; font-family: Monaco, Consolas, "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; font-size: 12px; height: auto !important; left: auto !important; line-height: 1.1em !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important;">//raw</code><code class="bash plain" style="-webkit-box-shadow: none !important; background-color: white; background-image: none !important; border-bottom-left-radius: 0px !important; border-bottom-right-radius: 0px !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border: 0px !important; bottom: auto !important; box-shadow: none !important; box-sizing: content-box !important; direction: ltr !important; display: inline !important; float: none !important; font-family: Monaco, Consolas, "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; font-size: 12px; height: auto !important; left: auto !important; line-height: 1.1em !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important;">.githubusercontent.com</code><code class="bash plain" style="-webkit-box-shadow: none !important; background-color: white; background-image: none !important; border-bottom-left-radius: 0px !important; border-bottom-right-radius: 0px !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border: 0px !important; bottom: auto !important; box-shadow: none !important; box-sizing: content-box !important; direction: ltr !important; display: inline !important; float: none !important; font-family: Monaco, Consolas, "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; font-size: 12px; height: auto !important; left: auto !important; line-height: 1.1em !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important;">/fnproject/cli/master/install</code><span style="background-color: white; color: #606569; font-family: "monaco" , "consolas" , "bitstream vera sans mono" , "courier new" , "courier" , monospace; font-size: 12px;"> </span><code class="bash plain" style="-webkit-box-shadow: none !important; background-color: white; background-image: none !important; border-bottom-left-radius: 0px !important; border-bottom-right-radius: 0px !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border: 0px !important; bottom: auto !important; box-shadow: none !important; box-sizing: content-box !important; direction: ltr !important; display: inline !important; float: none !important; font-family: Monaco, Consolas, "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; font-size: 12px; height: auto !important; left: auto !important; line-height: 1.1em !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important;">> setup.sh</code><br />
<code class="bash plain" style="-webkit-box-shadow: none !important; background-color: white; background-image: none !important; border-bottom-left-radius: 0px !important; border-bottom-right-radius: 0px !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border: 0px !important; bottom: auto !important; box-shadow: none !important; box-sizing: content-box !important; direction: ltr !important; display: inline !important; float: none !important; font-family: Monaco, Consolas, "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; font-size: 12px; height: auto !important; left: auto !important; line-height: 1.1em !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important;">chmod u+x setup.sh</code><br />
<code class="bash plain" style="-webkit-box-shadow: none !important; background-color: white; background-image: none !important; border-bottom-left-radius: 0px !important; border-bottom-right-radius: 0px !important; border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border: 0px !important; bottom: auto !important; box-shadow: none !important; box-sizing: content-box !important; direction: ltr !important; display: inline !important; float: none !important; font-family: Monaco, Consolas, "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important; font-size: 12px; height: auto !important; left: auto !important; line-height: 1.1em !important; margin: 0px !important; outline: 0px !important; overflow: visible !important; padding: 0px !important; position: static !important; right: auto !important; top: auto !important; vertical-align: baseline !important; width: auto !important;">sudo ./setup.sh</code><br />
<br />
Install Fn on K8s cluster with Helm (assuming you do have git client):<br />
<pre class="java" name="code">git clone git@github.com:fnproject/fn-helm.git && cd fn-helm
helm dep build fn</pre>
<pre class="java" name="code">helm install --name fn-release fn</pre>
<pre style="border: 0px; box-sizing: inherit; font-stretch: inherit; line-height: inherit; margin-bottom: 1em; max-height: 600px; orphans: 2; overflow: auto; padding: 5px; vertical-align: baseline; widows: 2; width: auto; word-wrap: normal;"></pre>
Wait (a couple of minutes) until Google Kubernetes Engine assigns an external IP to the Fn API in the cluster. Check it with:<br />
<pre class="java" name="code">kubectl get svc --namespace default -w fn-release-fn-api
</pre>
<br />
Configure your local Fn client with access to Fn running on K8s cluster
<br />
<pre class="java" name="code">export FN_API_URL=http://$(kubectl get svc --namespace default fn-release-fn-api -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):80
</pre>
<br />
Basically, it's done. Let's check it:<br />
<pre class="java" name="code"> fn apps create adfbuilderapp
fn apps list
</pre>
<br />
Now we can build ADF applications with an Fn function as it is described in my <a href="https://adfpractice-fedor.blogspot.com/2018/01/fn-function-to-build-oracle-adf.html">previous post</a>. Only this time the function will run and therefore building job will be performed somewhere high in the cloud.<br />
<br />
<br />
That's it!</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-78000268268663976402018-02-26T17:45:00.000-08:002018-02-26T17:55:45.676-08:00Running Tomcat and Oracle DB in a Docker container<div dir="ltr" style="text-align: left;" trbidi="on">
In one of my <a href="http://adfpractice-fedor.blogspot.com/2018/01/running-adf-essentials-on-tomcat-in.html">previous posts </a>I showed how to run an ADF essentials application on Tomcat in a docker container. I am using this approach primarily for sample applications as a convenient way to share a proof-of-concept. In this post I am going to describe how to enrich the docker container with Oracle DB so my samples can be DB aware.<br />
<br />
The original Tomcat image that I am developing in these posts is based on Debian Linux. I really don't want to have fun with installing and configuring Oracle DB on Debian Linux, and, for sure, I am not going to describe that in this post. What I am going to do is to use Docker-in-Docker technique. So, I am going to take the container from the previous post with ADF-preconfigured Tomcat, install Docker runtime in that container, pull Oracle DB image and run it inside the container. There are plenty of discussions about the Docker-in-Docker technique arguing if it is effective enough or not. I think I wouldn't go with this approach in production, but for sample applications I am totally fine with it.<br />
<br />
Let's start.<br />
<br />
1. Run a new container from the image saved in <a href="http://adfpractice-fedor.blogspot.com/2018/01/running-adf-essentials-on-tomcat-in.html">the previous post</a>:<br />
<pre class="java" name="code">docker run --privileged -it -p 8888:8080 -p 1521:1521 -p 5500:5500 --name adftomcatdb efedorenko/adftomcat bash
</pre>
Mind the option <b>privileged</b> in the docker command. This option is needed to make the container able to run Docker engine inside itself.<br />
<br />
2. Install Docker engine in the container:<br />
<pre class="java" name="code">curl -fsSL get.docker.com -o get-docker.sh
sh get-docker.sh
</pre>
After successful installation Docker engine should start automatically. It can be checked by running a simple docker command:<br />
<pre class="java" name="code">docker ps
</pre>
If the engine has not started (as it happened in my case), start it manually:<br />
<pre class="java" name="code">service docker start</pre>
3. Login to Docker Hub:<br />
<pre class="java" name="code">docker login</pre>
And provide your Docker Hub credentials.<br />
<br />
4. Pull and run official <a href="https://store.docker.com/images/oracle-database-enterprise-edition/plans/08cf8677-bb8f-453c-b667-6b0c24a388d4?tab=instructions">Oracle DB Image</a>:<br />
docker run --detach=true --name ADFDB -p 1521:1521 -p 5500:5500 store/oracle/database-enterprise:12.2.0.1<br />
<br />
It's done!<br />
<br />
Now we have a docker container with preconfigured Tomcat to run ADF applications and with Oracle DB running in a container inside the container. We can connect to the DB from both <b>adftomcatdb </b>container and the host machine as <b>sys/Oradoc_db1@127.0.0.1:1521:ORCLDB</b> as sysdba<br />
<br />
Let's save our work to a docker image, so that we can reuse it later.<br />
<br />
5. Create a start up shell script <b>/user/local/tomcat/start.sh</b> in the container with the following content:<br />
<pre class="java" name="code"><div style="background-color: white; font-family: Menlo; font-size: 11px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">#!/bin/bash</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; font-stretch: normal; line-height: normal; min-height: 13px;">
service docker start</div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; font-stretch: normal; line-height: normal; min-height: 13px;">
docker start ADFDB</div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; font-stretch: normal; line-height: normal; min-height: 13px;">
catalina.sh start</div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; font-stretch: normal; line-height: normal; min-height: 13px;">
exec "$@"</div>
</pre>
6. Remove Docker runtimes folder in the container:<br />
<pre class="java" name="code"><div style="background-color: white; font-family: Menlo; font-size: 11px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">rm -r /var/lib/docker/runtimes/</span></div>
</pre>
7. Stop the container from the host terminal:<br />
<pre class="java" name="code"><div style="background-color: white; font-family: Menlo; font-size: 11px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"> docker stop adftomcatdb</span></div>
</pre>
8. Create a new image:<br />
<pre class="java" name="code"><div style="background-color: white; font-family: Menlo; font-size: 11px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">docker commit adftomcatdb efedorenko/adftomcatdb:1.0</span></div>
</pre>
9. Run a new container out of the created image:<br />
<pre class="java" name="code"><div style="background-color: white; font-family: Menlo; font-size: 11px; font-stretch: normal; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">docker run --privileged -it -p 8888:8080 -p 1521:1521 -p 5500:5500 --name adftomcatdb_10 efedorenko/adftomcatdb:1.0 ./start.sh bash</span></div>
</pre>
10. Enjoy!<br />
<br />
<br />
That's it!</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-66650309537831895192018-01-31T12:48:00.000-08:002018-02-03T12:51:11.099-08:00Fn Function to build an Oracle ADF application<div dir="ltr" style="text-align: left;" trbidi="on">
<div dir="ltr" style="text-align: left;" trbidi="on">
In one of my <a href="http://adfpractice-fedor.blogspot.com/2017/12/building-oracle-adf-applications-with.html">previous posts</a> I described how to create a Docker container serving as a builder machine for ADF applications. Here I am going to show how to use this container as a <b>function</b> on Fn platform.<br />
<br />
First of all let's update the container so that it meets requirements of a function, meaning that it can be invoked as a runnable binary accepting some arguments. In an empty folder I have created a Dockerfile (just a simple text file with this name) with the following content:<br />
<br />
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">FROM efedorenko/adfbuilder</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">ENTRYPOINT ["xargs","mvn","package","-DoracleHome=/opt/Oracle_Home","-f"]</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
This file contains instructions for Docker on how to create a new Docker image out of existing one (efedorenko/adfbuilder from the previous post) and specifies an entry point, so that a container knows what to do once it has been initiated by the Docker <b>run</b> command. In this case whenever we run a container it executes Maven <b>package</b> goal for the <b>pom</b> file with the name fetched from <b>stdin</b>. This is important as Fn platform uses <b>stdin/stdout</b> for functions input/output as a standard approach.<br />
<br />
In the same folder let's execute a command to build a new Docker image (fn_adfbuilder) out of our Docker file:<br />
<br />
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">docker build -t efedorenko/fn_adfbuilder .</span></div>
<br />
Now, if we run the container passing <b>pom</b> file name through <b>stdin</b> like this:<br />
<br />
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">echo -n "/opt/MySampleApp/pom.xml" | docker run -i --rm efedorenko/fn_adfbuilder</span></div>
<br />
The container will execute inside itself what we actually need:<br />
<br />
<span style="background-color: white; font-family: "menlo"; font-size: 11px;">mvn package -DoracleHome=/opt/Oracle_Home -f </span><span style="background-color: white; font-family: "menlo"; font-size: 11px;">/opt/MySampleApp/pom.xml</span><br />
<br />
Basically, having done that, we got a container acting as a function. It builds an application for the given <b>pom </b>file.<br />
<br />
Let's use this function in Fn platform. The installation of Fn on your local machine is as easy as invoking a single command and described on <a href="https://github.com/fnproject/fn">GitHub Fn project</a> page. Once Fn is installed we can specify Docker registry where we store images of our functions-containers and start Fn server:<br />
<br />
<span style="background-color: white; font-family: "menlo"; font-size: 11px;">export FN_REGISTRY=efedorenko </span><br />
<span style="background-color: white; font-family: "menlo"; font-size: 11px;">fn start</span><br />
<br />
The next step is to create an Fn application which is going to use our awesome function:<br />
<br />
<span style="background-color: white; font-family: "menlo"; font-size: 11px;">fn apps create adfbuilderapp</span><br />
<span style="background-color: white; font-family: "menlo"; font-size: 11px;"><br /></span>
For this newly created app we have to specify a route to our function-confiner, so that the application knows when and how to invoke it:<br />
<br />
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">fn routes create --memory 1024 --timeout 3600 --type async adfbuilderapp /build efedorenko/fn_adfbuilder:latest</span></div>
<br />
We have created a route saying that whenever <b>/build</b> resource is requested for <b>adfbuilderapp</b>, Fn platform should create a new Docker container basing on the latest version of <b>fn_adfbuilder</b> image from <b> efedorenko</b> repository and run it granting with 1GB of memory and passing arguments to <b>stdin </b>(the default mode).<b> </b>Furthermore, since the building is a time/resource consuming job, we're going to invoke the function in async mode with an hour timeout. Having the route created we are able to invoke the function with <b>Fn Cli</b>:<br />
<br />
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">echo -n "/opt/MySampleApp/pom.xml" | fn call adfbuilderapp /build</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; line-height: normal;">
<div style="font-family: menlo; font-size: 11px;">
or over http:
</div>
<div style="background-color: white; font-family: menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><span style="font-family: -webkit-standard; font-size: small;"><br /></span></span></div>
<div style="background-color: white; font-family: menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"></span></div>
<div style="background-color: white; font-family: menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">curl -d "/opt/MySampleApp/pom.xml" http://localhost:8080/r/adfbuilderapp/build</span></div>
<div style="font-family: menlo; font-size: 11px;">
<br /></div>
In both cases the platform will put the call in a queue (since it is async) and return the call id:<br />
<div style="font-family: menlo; font-size: 11px;">
<br /></div>
<div style="font-family: menlo; font-size: 11px;">
<span style="background-color: white; font-family: "menlo"; font-size: 11px;">{"call_id":"01C5EJSJC847WK400000000000"}</span>
</div>
<div style="font-family: menlo; font-size: 11px;">
<br /></div>
<div style="font-family: menlo; font-size: 11px;">
<br /></div>
The function is working now and we can check how it is going in a number of different ways. Since function invocation is just creating and running a Docker container, we can see it by getting a list of all running containers:<br />
<div style="font-family: menlo; font-size: 11px;">
<br /></div>
<div style="font-family: menlo; font-size: 11px;">
<br /></div>
<div style="font-family: menlo; font-size: 11px;">
<span style="background-color: white; font-family: "menlo"; font-size: 11px;">docker ps </span></div>
</div>
<br />
<div style="background-color: white; font-family: Menlo; font-size: 8px; line-height: normal;">
<span style="background-color: white; font-family: "menlo"; font-size: 8px;"><span style="font-variant-ligatures: no-common-ligatures;">CONTAINER ID IMAGE CREATED STATUS NAMES</span></span></div>
<span style="background-color: white; font-family: "menlo"; font-size: 11px;">
</span>
<br />
<div style="background-color: white; font-family: Menlo; font-size: 8px; line-height: normal;">
<span style="background-color: white; font-family: "menlo"; font-size: 8px;"><span style="font-variant-ligatures: no-common-ligatures;">6e69a067b714 efedorenko/fn_adfbuilder:latest 3 seconds ago Up 2 seconds 01C5EJSJC847WK400000000000</span></span></div>
<span style="background-color: white; font-family: "menlo"; font-size: 8px;">e957cc54b638 fnproject/ui 21 hours ago Up 21 hours clever_turing</span><br />
<span style="background-color: white; font-family: "menlo"; font-size: 8px;">68940f3f0136 fnproject/fnserver 27 hours ago Up 27 hours fnserver</span><br />
<span style="background-color: white; font-family: "menlo"; font-size: 11px;">
</span>
<br />
<div>
<span style="background-color: white; font-family: "menlo"; font-size: 11px;"><span style="font-variant-ligatures: no-common-ligatures;"><br /></span></span></div>
<br />
Fn has created a new container and used function call id as its name. We can attach our stdin/stdout to the container and see what is happening inside:<br />
<br />
<span style="background-color: white; font-family: "menlo"; font-size: 11px;">docker attach 01C5EJSJC847WK400000000000</span><br />
<br />
Once the function has executed we can use Fn Rest API (or Fn Cli) to request information about the call:<br />
<br />
<span style="background-color: white; font-family: "menlo"; font-size: 11px;">http://localhost:8080/v1/apps/adfbuilderapp/calls/</span><span style="background-color: white; font-family: "menlo"; font-size: 11px;">01C5EJSJC847WK400000000000</span><br />
<span style="white-space: pre-wrap;"><br /></span>
<span style="white-space: pre-wrap;">{"message":"Successfully loaded call","call":{"id":"</span><span style="background-color: white; font-family: "menlo"; font-size: 11px;">01C5EJSJC847WK400000000000</span><span style="white-space: pre-wrap;">","status":"success","app_name":"adfbuilderapp","path":"/build","completed_at":"2018-02-03T19:52:33.204Z","created_at":"2018-02-03T19:46:56.071Z","started_at":"2018-02-03T19:46:57.050Z","stats":[{"timestamp":"2018-02-03T19:46:58.189Z","metrics":</span><br />
<pre style="white-space: pre-wrap; word-wrap: break-word;">....</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">
</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">
</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="background-color: white; font-family: "menlo"; font-size: 11px; white-space: normal;">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="background-color: white; font-family: "menlo"; font-size: 11px; white-space: normal;">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="background-color: white; font-family: "menlo"; font-size: 11px; white-space: normal;">http://localhost:8080/v1/apps/adfbuilderapp/calls/</span><span style="background-color: white; font-family: "menlo"; font-size: 11px; white-space: normal;">01C5EJSJC847WK400000000000/log</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="background-color: white; font-family: "menlo"; font-size: 11px; white-space: normal;">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="background-color: white; font-family: "menlo"; font-size: 11px; white-space: normal;">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">{"message":"Successfully loaded log","log":{"call_id":"01C5EKA5Y747WK600000000000","log":"[INFO] Scanning for projects...\n[INFO] ------------------------------------------------------------------------\n[INFO] Reactor Build Order:\n[INFO] \n[INFO] Model\n[INFO] ViewController\n[INFO]</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;">....</pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="background-color: white; font-family: "menlo"; font-size: 11px; white-space: normal;">
</span></pre>
<pre style="white-space: pre-wrap; word-wrap: break-word;"><span style="background-color: white; font-family: "menlo"; font-size: 11px; white-space: normal;">
</span></pre>
<br />
We can also monitor function calls in a fancy way by using <a href="https://github.com/fnproject/ui">Fn UI</a> dashboard:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-z2hK1F-YgjY/WnYW_Xwb61I/AAAAAAAABtI/-YtOTpjIIWwfnX1WjIsBeRrGSxiacM0BQCLcBGAs/s1600/Screen%2BShot%2B2018-02-03%2Bat%2B2.08.43%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="943" data-original-width="1600" height="235" src="https://1.bp.blogspot.com/-z2hK1F-YgjY/WnYW_Xwb61I/AAAAAAAABtI/-YtOTpjIIWwfnX1WjIsBeRrGSxiacM0BQCLcBGAs/s400/Screen%2BShot%2B2018-02-03%2Bat%2B2.08.43%2BPM.png" width="400" /></a></div>
<br />
<br />
The result of our work is a function that builds ADF applications. The beauty of it is that the consumer of the function, the caller, just uses Rest API over http to get the application built and the caller does not care how and where this job will be done. But the caller knows for sure that computing resources will be utilized no longer than it is needed to get the job done.<br />
<br />
Next time we'll try to orchestrate the function in Fn Flow.<br />
<br />
That's it!<br />
<br />
<div>
<span style="background-color: white; font-family: "menlo"; font-size: 11px;"><br /></span></div>
<span style="background-color: white; font-family: "menlo"; font-size: 11px;">
<br />
<br />
</span></div>
</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-33795068463955228412018-01-27T15:20:00.000-08:002018-01-27T15:20:44.996-08:00Running ADF Essentials on Tomcat in a Docker container<div dir="ltr" style="text-align: left;" trbidi="on">
I develop sample applications pretty often. I try out some ideas, play with some techniques and share the result of my investigations with my colleagues and blog readers by the means of sample applications. When someone wants to see how the technique was implemented they just look into the source code and that's enough to get the idea. But if they want to see how it actually works and play with it, they need to find the right version of JDeveloper, start it, run the sample application and, probably, dance a little with a tambourine to get it working. Too complicated and not fun. What would be fun is to have a lightweight Docker container with deployed sample application which everyone can easily run on their Docker environment. In this post I am going to show what I did to create a preconfigured docker-image-template which I will use to create images with deployed sample applications.<br />
<br />
Since the key is to have a lightweight container and since my sample ADF applications rarely go beyond essentials functionality I decided to create a Docker container running Tomcat with ADF Essentials on top of that.<br />
<br />
So, let's start:<br />
<br />
1. Pull and run Tomcat image from Docker hub:<br />
<br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">docker run -it -p 8888:8080 --name adftomcat tomcat:8.0</span><span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px; white-space: pre;"> </span><br />
<br />
Having done that, you would be able to observe the running Tomcat here http://localhost:8888.<br />
<br />
2. Install the latest Java in the container:<br />
<br />
In a separate terminal window dive into the container:<br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">
docker exec -it adftomcat bash</span><br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;"><br /></span>
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;"></span><br />
And install Java:<br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">
apt-get update</span><br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">apt-get install software-properties-common </span><br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">add-apt-repository "deb http://ppa.launchpad.net/webupd8team/java/ubuntu xenial main"</span><br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">apt-get update </span><br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">apt-get install oracle-java8-installer
</span><span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px; white-space: pre;"> </span><br />
<br />
3. Download ADF Essentials (including client) from <a href="http://www.oracle.com/technetwork/developer-tools/adf/downloads/adf-download-1649592.html">Oracle Website</a><br />
<br />
This will give you to archives: adf-essentials.zip and adf-essentials-client-ear.zip. Copy them in the container:<br />
<br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">docker cp ~/Downloads/adf-essentials.zip adftomcat:/usr/local/tomcat/lib</span><br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">docker cp ~/Downloads/adf-essentials-client-ear.zip adftomcat:/usr/local/tomcat/lib
</span><br />
<br />
Go to the container (<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">docker exec -it adftomcat bash</span>) and unzip them with -j option:<br />
<br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">unzip -j </span><span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">/usr/local/tomcat/lib/</span><span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">adf-essentials.zip</span><br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">unzip -j </span><span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">/usr/local/tomcat/lib/</span><span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">adf-essentials-client-ear.zip</span><br />
<br />
4. Download javax.mail-api-1.4.6.jar from <a href="http://www.java2s.com/Code/Jar/j/Downloadjavaxmailapi146jar.htm">here</a> and copy it into the container:<br />
<br />
<span style="background-color: white; font-family: "menlo"; font-size: 11px;"></span><span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">docker cp ~/Downloads/javax.mail-api-1.4.6.jar adftomcat:/usr/local/tomcat/lib</span><br />
<br />
5. Install <i>nano </i>text editor in the container:<br />
<br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px; white-space: pre;"></span>
<span style="background-color: white; font-family: "menlo"; font-size: 11px;"></span><span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">apt-get install nano</span><br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px; white-space: pre;"><br /></span>
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px; white-space: pre;"><br /></span>
6. In the container create <i>setenv.sh</i> file in <span style="background-color: white; font-family: "menlo"; font-size: 11px;">/usr/local/tomcat/bin </span>folder:<br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px; white-space: pre;"></span><br />
<span style="background-color: white; font-family: "menlo"; font-size: 11px;"></span><span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">nano /usr/local/tomcat/bin/setenv.sh</span><br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;"><br /></span>
<br />
<div>
With the following content:</div>
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px; white-space: pre;"><br /></span>
<br />
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-family: "menlo";">JAVA_HOME=/usr/lib/jvm/java-8-oracle</span><br />
<div style="font-family: Menlo; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">CATALINA_OPTS='-Doracle.mds.cache=simple -Dorg.apache.el.parser.SKIP_IDENTIFIER_CHECK=true'</span></div>
</div>
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px; white-space: pre;"><br /></span>
<br />
7. In the container update <span style="background-color: white; font-family: "menlo"; font-size: 11px;">/usr/local/tomcat/conf/context.xml </span>file:<br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px; white-space: pre;"></span><br />
<span style="background-color: rgba(0 , 0 , 0 , 0.0470588); color: #3a3a3a; font-family: monospace; font-size: 14px; white-space: pre;"></span><br />
<span style="background-color: white; font-family: "menlo"; font-size: 11px;"></span><span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">nano /usr/local/tomcat/conf/context.xml</span><br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;"><br /></span>
And add the following line in the <i><Context> </i>section<br />
<span style="background-color: white; font-family: "menlo"; font-size: 11px;"><br /></span>
<span style="background-color: white; font-family: "menlo"; font-size: 11px;"><JarScanner scanManifest="false"/></span><br />
<br />
<div>
8. Basically, this is enough to deploy an ADF application to the container. I created an image out of this preconfigured container for future uses as a template. </div>
<div>
<br /></div>
<div>
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">docker commit adftomcat efedorenko/adftomcat</span></div>
<div>
<br /></div>
<div>
9. Develop a "Tomcat-compatable" sample ADF application (check Chandresh's <a href="https://flexagon.com/2017/02/how-to-adf-essentials-12c-application-on-tomcat-8/">blog</a> describing how to create an ADF application suitable for Tomcat). Deploy it to a war and copy the war into the container:</div>
<div>
<br /></div>
<div>
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">docker cp tcatapp.war adftomcat:/usr/local/tomcat/webapps</span></div>
<div>
<br />
10. Restart the container<br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;"><br /></span>
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">docker stop adftomcat</span><br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;">docker start -I adftomcat</span><br />
<span style="background-color: rgba(0 , 0 , 0 , 0.85098); color: #f4f4f4; font-family: "monaco"; font-size: 10px;"><br /></span>
11. Check the application availability here http://localhost:8888/MY_CONTEXT_ROOT/faces/main.jsf<br />
<br />
<br />
12. Now we can create an image out of this container, run it in a docker cloud or just share it with your colleagues so they can run it wherever they prefer.<br />
<br />
<br />
That's it!<br />
<br /></div>
<span style="color: #f4f4f4; font-family: "monaco"; font-size: xx-small;"><span style="background-color: rgba(0, 0, 0, 0.85098); white-space: pre;"><br /></span></span></div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-50632203795858007142017-12-30T14:59:00.000-08:002018-01-16T16:56:26.758-08:00Referring to ADF Faces component in EL expression<div dir="ltr" style="text-align: left;" trbidi="on">
EL expressions are commonly used to specify attribute values of ADF Faces components on our page. It is interesting to know that we can use <b>component</b> keyword to refer to the component instance for which the EL expression is being evaluated. This is slightly similar to <b>this </b>in Java.<br />
<br />
For example, in the following snippet the button's <b>hint</b> is evaluated as the button's <b>text</b> value and its <b>visible</b> attribute is going to be returned by a backing bean method accepting the component as a parameter:<br />
<br />
<pre class="java" name="code"><af:button text="#{theBean.buttonText}" id="b1"
shortDesc="#{component.text}" visible="#{theBean.isVisible(component)}"/>
</pre>
The backing bean method may look like this:<br />
<pre class="java" name="code"> public boolean isVisible(UIComponent button)
{
//Do something with the button
((RichButton) button).setIcon("images/awesomeIcon.jpg");
//check button's attributes
if (button. ...)
return true;
else
return false;
}
</pre>
<br />
This technique could be quite useful when it comes to rendering components inside some iterator (or list view or table, etc.) and we need to evaluate component's attribute value dynamically depending on the exact component instance.<br />
<br />
That's it!<br />
<br />
<br /></div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-35298934747755700662017-12-28T11:29:00.000-08:002018-01-06T11:34:45.764-08:00Building Oracle ADF applications with Docker<div dir="ltr" style="text-align: left;" trbidi="on">
Recently a good friend of mine was facing a regular problem with building an ADF application v.12.2.1.2 with the public Oracle Maven Repository. He asked me to check if it worked for me. Well... it didn't. So, there was some problem with the repository. In order to make the experiment clean and to avoid any impact on my working environment I decided to run the test in a docker container. And even though I could not help my friend (it simply didn't work throwing some dependency exception), as the result of this check I got a reusable docker image which serves as a preconfigured building machine for ADF applications (for v. 12.2.1.3 the Oracle Maven Repository worked fine at that moment). <br />
<br />
This is what I did:<br />
<br />
1. Pull and run an <b>ubuntu</b> Docker image<br />
<br />
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">$: docker run -it --name adfbuilder ubuntu</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
2. Install <b>Java</b> in the <b>adfbuilder</b> container<br />
<br />
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
apt-get install software-properties-common python-software-properties</div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">add-apt-repository ppa:webupd8team/java</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">apt-get update</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">apt-get install oracle-java8-installer</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
3. Install <b>Maven</b> in the <b>adfbuilder</b> container<br />
<br />
Just download <b>maven</b> binaries and unzip them in some folder and copy into the container:<br />
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">docker cp ~/Downloads/apache-maven-3.5.2 adfbuilder:/opt/apache-maven-3.5.2</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">Update PATH environment variable in the container</span></div>
<div style="background-color: white; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">export PATH=$PATH:/opt/apache-maven-3.5.2/bin</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">Having done that, the <b>mvn </b>should be available. Run it in the container and it will create a hidden <b>.m2</b> folder in the user's home.</span></div>
<div style="background-color: white; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">4. Configure <b>Maven</b> </span><span style="background-color: transparent;">in the </span><b style="background-color: transparent;">adfbuilder</b><span style="background-color: transparent;"> container </span>to work with Oracle Maven Repository<br />
<br /></div>
<div style="background-color: white; line-height: normal;">
<span style="background-color: transparent;">Just put in the </span>hidden <b style="background-color: transparent;">.m2</b> folder </div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"> docker cp settings.xml adfbuilder:/root/.m2/settings.xml</span></div>
<div style="background-color: white; line-height: normal;">
<br /></div>
<div style="background-color: white; line-height: normal;">
settings.xml file with the following content:</div>
<pre class="java" name="code"><settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>maven.oracle.com</id>
<username>eugene.fedorenko@flexagon.com</username>
<password><MY_PASSWORD></password>
<configuration>
<basicAuthScope>
<host>ANY</host>
<port>ANY</port>
<realm>OAM 11g</realm>
</basicAuthScope>
<httpConfiguration>
<all>
<params>
<property>
<name>http.protocol.allow-circular-redirects</name>
<value>%b,true</value>
</property>
</params>
</all>
</httpConfiguration>
</configuration>
</server>
</servers>
<profiles>
<profile>
<id>main</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<id>maven.oracle.com</id>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<url>https://maven.oracle.com</url>
<layout>default</layout>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>maven.oracle.com</id>
<url>https://maven.oracle.com</url>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</settings>
</pre>
Basically, this is enough to compile a Maven-configured ADF application in the container. We need to make sure that there is an access to the source code of our application from the container. This can be done either by mapping a source folder to be visible from the container or just by coping it into the container.<br />
<br />
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">docker cp /mywork/MySampleApp adfbuilder:/opt/</span>MySampleApp</div>
<br />
Having done that, we can run the following command to get the application compiled:<br />
<br />
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">docker exec adfbuilder mvn -f /opt/MySampleApp/pom.xml compile</span></div>
<br />
5. Copy JDeveloper binaries into the container<br />
As we want to go beyond this point and be able not only to compile, but to produce deployable artifacts (ears, jars, etc.), we will need to put JDeveloper binaries into the container (basically, <b>maven </b>will need <b>ojdeploy).</b> I have just copied Oracle_Home folder from my Mac to the container:<br />
<br />
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">docker cp /My_Oracle_Home adfbuilder:/opt/Oracle_Home</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
So, now I am able to build a <b>ear</b> for my application in the container:<br />
<br />
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">docker exec adfbuilder mvn -f /opt/MySampleApp/pom.xml package -DoracleHome=/opt/Oracle_Home</span></div>
<br />
For the first run it may ask you to provide you the path to your JDK<br />
<br />
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">[INFO] Type the full pathname of a JDK installation (or Ctrl-C to quit), the path will be stored in /root/.jdeveloper/12.2.1.3.0/product.conf</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">/usr/lib/jvm/java-8-oracle</span></div>
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;"><br /></span></div>
6. Commit changes to the container<br />
The final thing we need to do is to commit changes to the container:<br />
<br />
<div style="background-color: white; font-family: Menlo; font-size: 11px; line-height: normal;">
<span style="font-variant-ligatures: no-common-ligatures;">docker commit adfbuilder efedorenko/adfbuilder</span></div>
<br />
This will create a new <b>ubuntu</b> image containing all changes that we applied. We can easily run that image wherever we want across our infrastructure and use it as a building machine for ADF applications. The beauty of it is that we can run it in a cloud like Docker Cloud (backed by AWS, Microsoft Azure, Digital Ocean, etc.) or Oracle Container Cloud Services or whatever you prefer. With this approach servers in the cloud build your application for you which in general is a quite resource-consuming job.<br />
<br />
Thant's it!<br />
<br />
<br />
<br />
<br />
<br />
</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0tag:blogger.com,1999:blog-8727965405539153082.post-67546820440980446732017-11-30T10:39:00.000-08:002017-12-04T10:40:15.579-08:00Creating a View Object Row with ADF Bindings CreateInsert action<div dir="ltr" style="text-align: left;" trbidi="on">
In this short post I am going to highlight a small pitfall related to a very common approach to create a new record in a task flow.<br />
Let's consider an example of a simple task flow creating a new VO row, displaying that row on a page fragment and committing the transaction if the user clicks "Ok" button:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-8W3Ma6NLZk8/WiWP1bEzn8I/AAAAAAAABrE/mUNOsgyQjy8kfiqzHJWK4vxBXIOm1iH8gCLcBGAs/s1600/Screen%2BShot%2B2017-12-04%2Bat%2B12.09.01%2BPM.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="363" data-original-width="1600" height="144" src="https://2.bp.blogspot.com/-8W3Ma6NLZk8/WiWP1bEzn8I/AAAAAAAABrE/mUNOsgyQjy8kfiqzHJWK4vxBXIOm1iH8gCLcBGAs/s640/Screen%2BShot%2B2017-12-04%2Bat%2B12.09.01%2BPM.png" width="640" /></a></div>
<br />
<br />
The <b>CreateInsert </b>method<b> </b>call has been just dragged&dropped from the data control palette. The thing is that if the user does not update any VO attributes in <b>view1 </b>page fragment, the <b>Commit </b>method call will do nothing. The new row will not be posted to the database.<br />
The reason for this behavior is that the ADF bindings <b>CreateInsert</b> action always creates an entity in <b>Initialized</b> state, which is ignored by the frameworks while committing the transaction. Even if the entity has default values, or it's <b>Create</b> method is overridden setting the attribute values, it doesn't matter, the entity will be still in <b>Initialized</b> state after the <b>CreateInsert </b>action. Afterwords, if any VO attributes are modified, the entity gets the <b>New </b>status and the framework will post changes (preform <b>insert</b> statement) while committing the transaction. This behavior is quite logical as in most cases task flows like that create a view object row to get it updated by the user before submitting to the database. However, most cases are not all and if it is needed we can always implement a custom VO method creating/inserting a new row and invoke it instead of the standard <b>CreateInsert</b> action. Like this one:<br />
<br />
<pre class="java" name="code"> public void addNewEmployee() {
EmployeeViewRowImpl row = (EmployeeViewRowImpl) createRow();
insertRow(row);
}
</pre>
<div>
<br /></div>
That's it!</div>
Eugene Fedorenkohttp://www.blogger.com/profile/05514757367086948632noreply@blogger.com0