best way in Kubernetes to receive notifications of errors when instantiating resources from .json or .yaml specifications? - kubernetes

I am using fabric8 to develop a cluster management layer on top of Kubernetes, and I am confused as to what the
'official' API is to obtain notifications of errors when things go wrong when
instantiating pods/rep controllers & services etc.
In the section "Pod Deployment Code" I have a stripped down version of what we do for pods. In the event
that everything goes correctly, our code is fine. We rely on setting 'watches' as you
can see in the method deployPodWithWatch. All I do in the given eventReceived callback
is to print the event, but our real code will break apart a notification like this:
got action:
MODIFIED /
Pod(apiVersion=v1, kind=Pod, metadata=...etc etc
status=PodStatus(
conditions=[
and pick out the 'status' element of the Pod and when we get PodCondition(status=True, type=Ready), we know
that our pod has been successfully deployed.
In the happy path case this works great. And you can actually run the code supplied with variable
k8sUrl set to the proper url for your site (hopefully your k8s
installation does not require auth which is site specific so i didn't provide code for that).
However, suppose you change the variable imageName to "nginBoo". There is no public docker
image of that name, so after you run the code, set your kubernetes context to the namespace "junk",
and do a
describe pod podboy
you will see two status messages at the end with the following values for Reason / Message
Reason message
failedSync Error syncing pod, skipping...
failed Failed to pull image "nginBoo": API error (500):
Error parsing reference: "nginBoo"
is not a valid repository/tag
I would like to implement a watch callback so that it catches these types of errors. However,
the only thing that I see are 'MODIFIED' events wherein the Pod has a field like this:
state=ContainerState(running=null, terminated=null,
waiting=ContainerStateWaiting(
reason=API error (500):
Error parsing reference:
"nginBoo" is not a valid repository/tag
I suppose I could look for a reason code that contained the string 'API error' but this seems
to be very much an implementation-dependent hack -- it might not cover all cases, and maybe it will
change under my feet with future versions. I'd like some more 'official' way of
figuring out if there was an error, but my searches have come up dry -- so I humbly
request guidance from all of you k8s experts out there. Thanks !
Pod Deployment Code
import com.fasterxml.jackson.databind.ObjectMapper
import scala.collection.JavaConverters._
import com.ning.http.client.ws.WebSocket
import com.typesafe.scalalogging.StrictLogging
import io.fabric8.kubernetes.api.model.{DoneableNamespace, Namespace, Pod, ReplicationController}
import io.fabric8.kubernetes.client.DefaultKubernetesClient.ConfigBuilder
import io.fabric8.kubernetes.client.Watcher.Action
import io.fabric8.kubernetes.client.dsl.Resource
import io.fabric8.kubernetes.client.{DefaultKubernetesClient, Watcher}
object ErrorTest extends App with StrictLogging {
// corresponds to --insecure-skip-tls-verify=true, according to io.fabric8.kubernetes.api.model.Cluster
val trustCerts = true
val k8sUrl = "http://localhost:8080"
val namespaceName = "junk" // replace this with name of a namespace that you know exists
val imageName: String = "nginx"
def go(): Unit = {
val kube = getConnection
dumpNamespaces(kube)
deployPodWithWatch(kube, getPod(image = imageName))
}
def deployPodWithWatch(kube: DefaultKubernetesClient, pod: Pod): Unit = {
kube.pods().inNamespace(namespaceName).create(pod) /* create the pod ! */
val podWatchWebSocket: WebSocket = /* create watch on the pod */
kube.pods().inNamespace(namespaceName).withName(pod.getMetadata.getName).watch(getPodWatch)
}
def getPod(image: String): Pod = {
val jsonTemplate =
"""
|{
| "kind": "Pod",
| "apiVersion": "v1",
| "metadata": {
| "name": "podboy",
| "labels": {
| "app": "nginx"
| }
| },
| "spec": {
| "containers": [
| {
| "name": "podboy",
| "image": "<image>",
| "ports": [
| {
| "containerPort": 80,
| "protocol": "TCP"
| }
| ]
| }
| ]
| }
|}
""".
stripMargin
val replacement: String = "image\": \"" + image
val json = jsonTemplate.replaceAll("image\": \"<image>", replacement)
System.out.println("json:" + json);
new ObjectMapper().readValue(json, classOf[Pod])
}
def dumpNamespaces(kube: DefaultKubernetesClient): Unit = {
val namespaceNames = kube.namespaces().list().getItems.asScala.map {
(ns: Namespace) => {
ns.getMetadata.getName
}
}
System.out.println("namespaces are:" + namespaceNames);
}
def getConnection = {
val configBuilder = new ConfigBuilder()
val config =
configBuilder.
trustCerts(trustCerts).
masterUrl(k8sUrl).
build()
new DefaultKubernetesClient(config)
}
def getPodWatch: Watcher[Pod] = {
new Watcher[Pod]() {
def eventReceived(action: Action, watchedPod: Pod) {
System.out.println("got action: " + action + " / " + watchedPod)
}
}
}
go()
}

I'd suggest you to have a look at events, see this topic for some guidance. Generally each object should generate events you can watch and be notified of such errors.

Related

Referencing a loop object

i am currently checking out tanka + jsonnet. But evertime i think i understand it... sth. new irritates me. Can somebody help me understand how to do a loop-reference? (Or general better solution?)
Trying to create multiple deployments with a corresponding configmapVolumeMount and i am not sure how to reference to the according configmap object here?
(using a configVolumeMount it works since it refers to the name, not the object).
deployment: [
deploy.new(
name='demo-' + instance.name,
],
)
+ deploy.configMapVolumeMount('config-' + instance.name, '/config.yml', k.core.v1.volumeMount.withSubPath('config.yml'))
for instance in $._config.demo.instances
],
configMap: [
configMap.new('config-' + instance.name, {
'config.yml': (importstr 'files/config.yml') % {
name: instance.name,
....
},
}),
for instance in $._config.demo.instances
]
regards
Great to read that you're making progress with tanka, it's an awesome tool (once you learned how to ride it heh).
Find below a possible answer, see inline comments in the code, in particular how we ab-use tanka layout flexibility, to "populate" deploys: [...] array with jsonnet objects containing each paired deploy+configMap.
config.jsonnet
{
demo: {
instances: ['foo', 'bar'],
image: 'nginx', // just as example
},
}
main.jsonnet
local config = import 'config.jsonnet';
local k = import 'github.com/grafana/jsonnet-libs/ksonnet-util/kausal.libsonnet';
{
local deployment = k.apps.v1.deployment,
local configMap = k.core.v1.configMap,
_config:: import 'config.jsonnet',
// my_deploy(name) will return name-d deploy+configMap object
my_deploy(name):: {
local this = self,
deployment:
deployment.new(
name='deploy-%s' % name,
replicas=1,
containers=[
k.core.v1.container.new('demo-%s' % name, $._config.demo.image),
],
)
+ deployment.configMapVolumeMount(
this.configMap,
'/config.yml',
k.core.v1.volumeMount.withSubPath('config.yml')
),
configMap:
configMap.new('config-%s' % name)
+ configMap.withData({
// NB: replacing `importstr 'files/config.yml';` by
// a simple YAML multi-line string, just for the sake of having
// a simple yet complete/usable example.
'config.yml': |||
name: %(name)s
other: value
||| % { name: name }, //
}),
},
// Tanka is pretty flexible with the "layout" of the Kubernetes objects
// in the Environment (can be arrays, objects, etc), below using an array
// for simplicity (built via a loop/comprehension)
deploys: [$.my_deploy(name) for name in $._config.demo.instances],
}
output
$ tk init
[...]
## NOTE: using https://kind.sigs.k8s.io/ local Kubernetes cluster
$ tk env set --server-from-context kind-kind environments/default
[... save main.jsonnet, config.jsonnet to ./environments/default/]
$ tk apply --dry-run=server environments/default
[...]
configmap/config-bar created (server dry run)
configmap/config-foo created (server dry run)
deployment.apps/deploy-bar created (server dry run)
deployment.apps/deploy-foo created (server dry run)

JupyterHub - log current user

I use a custom logger to log who is currently doing any kind of stuff in Jupyterhub.
logging_config: dict = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"company": {
"()": lambda: MyFormatter(user=os.environ.get("JUPYTERHUB_USER", "Unknown"))
},
},
....
c.Application.logging_config = logging_config
Output:
{"asctime": "2022-06-29 14:13:43,773", "level": "WARNING", "name": "JupyterHub", "message": "Updating Hub route http://127.0.0.1:8081 \u2192 http://jupyterhub:8081", "user": "Unknown"
The logger itself works fine, but I am not able to log who was performing the action. In the Image I start, there is a JUPYTERHUB_USER env variable available. This seems to get passed from JupyterHub ( I don´t know how this is done exactly). But in JupyterHub I don´t have this variable available.
Is there a way to use it in JupyterHub, not just in the jupyterLab container?
This doesn't get you all the way there but it's a start - we add extra pod annotations/labels through KubeSpawner's extra_annotations using the cluster_options hook (see our helm chart for our complete daskhub setup):
dask-gateway:
gateway:
extraConfig:
optionHandler: |
from dask_gateway_server.options import Options, String, Select, Mapping, Float, Bool
from math import ceil
def cluster_options(user):
def option_handler(options):
extra_annotations = {
"hub.jupyter.org/username": user.name
}
default_extra_labels = {
"hub.jupyter.org/username": user.name,
}
return Options(
Select(
...
),
...,
handler=option_handler,
)
c.Backend.cluster_options = cluster_options
You can then poll pods with these labels to get real time usage. There may be a more direct way to do this though - not sure.

CannotPullContainerError: failed to extract layer

I'm trying to run a task on a windows container in fargate mode on aws
The container is a .net console application (Fullframework 4.5)
This is the task definition generated programmatically by SDK
var taskResponse = await ecsClient.RegisterTaskDefinitionAsync(new Amazon.ECS.Model.RegisterTaskDefinitionRequest()
{
RequiresCompatibilities = new List<string>() { "FARGATE" },
TaskRoleArn = TASK_ROLE_ARN,
ExecutionRoleArn = EXECUTION_ROLE_ARN,
Cpu = CONTAINER_CPU.ToString(),
Memory = CONTAINER_MEMORY.ToString(),
NetworkMode = NetworkMode.Awsvpc,
Family = "netfullframework45consoleapp-task-definition",
EphemeralStorage = new EphemeralStorage() { SizeInGiB = EPHEMERAL_STORAGE_SIZE_GIB },
ContainerDefinitions = new List<Amazon.ECS.Model.ContainerDefinition>()
{
new Amazon.ECS.Model.ContainerDefinition()
{
Name = "netfullframework45consoleapp-task-definition",
Image = "XXXXXXXXXX.dkr.ecr.eu-west-1.amazonaws.com/netfullframework45consoleapp:latest",
Cpu = CONTAINER_CPU,
Memory = CONTAINER_MEMORY,
Essential = true
//I REMOVED THE LOG DEFINITION TO SIMPLIFY THE PROBLEM
//,
//LogConfiguration = new Amazon.ECS.Model.LogConfiguration()
//{
// LogDriver = LogDriver.Awslogs,
// Options = new Dictionary<string, string>()
// {
// { "awslogs-create-group", "true"},
// { "awslogs-group", $"/ecs/{TASK_DEFINITION_NAME}" },
// { "awslogs-region", AWS_REGION },
// { "awslogs-stream-prefix", $"{TASK_DEFINITION_NAME}" }
// }
//}
}
}
});
these are the role policies contained used by the task AmazonECSTaskExecutionRolePolicy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
i got this error when lunch the task
CannotPullContainerError: ref pull has been retried 1 time(s): failed to extract layer sha256:fe48cee89971abac42eedb9110b61867659df00fc5b0b90dd91d6e19f704d935: link /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/212/fs/Files/ProgramData/Microsoft/Event Viewer/Views/ServerRoles/RemoteDesktop.Events.xml /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/212/fs/Files/Windows/Microsoft.NET/assembly/GAC_64/Microsoft.Windows.ServerManager.RDSPlugin/v4.0_10.0.0.0__31bf3856ad364e35/RemoteDesktop.Events.xml: no such file or directory: unknown
some search drived me here:
https://aws.amazon.com/it/premiumsupport/knowledge-center/ecs-pull-container-api-error-ecr/
the point 1 says that if i run the task on the private subnet (like i'm doing) i need a NAT with related route to garantee the communication towards the ECR, but
note that in my infrastructure i've a VPC Endpoint to the ECR....
so the first question is: is a VPC Endpoint sufficent to garantee the comunication from the container to the container images registry(ECR)? or i need necessarily to implement what the point 1 say (NAT and route on the route table) or eventually run the task on a public subnet?
Can be the error related to the missing communication towards the ECR, or could be a missing policy problem?
Make sure your VPC endpoint is configured correctly. Note that
"Amazon ECS tasks hosted on Fargate using platform version 1.4.0 or later require both the com.amazonaws.region.ecr.dkr and com.amazonaws.region.ecr.api Amazon ECR VPC endpoints as well as the Amazon S3 gateway endpoint to take advantage of this feature."
See https://docs.aws.amazon.com/AmazonECR/latest/userguide/vpc-endpoints.html for more information
In the first paragraph of the page I linked: "You don't need an internet gateway, a NAT device, or a virtual private gateway."

Isolation between kubernetes.admission policies in OPA

I use the vanilla Open Policy Agent as a deployment on Kubernetes for handling admission webhooks.
The behavior of multiple policies evaluation is not clear to me, see this example:
## policy-1.rego
package kubernetes.admission
check_namespace {
# evaluate to true
namespaces := {"namespace1"}
namespaces[input.request.namespace]
}
check_user {
# evaluate to false
users := {"user1"}
users[input.request.userInfo.username]
}
allow["yes - user1 and namespace1"] {
check_namespace
check_user
}
.
## policy-2.rego
package kubernetes.admission
check_namespace {
# evaluate to false
namespaces := {"namespace2"}
namespaces[input.request.namespace]
}
check_user {
# evaluate to true
users := {"user2"}
users[input.request.userInfo.username]
}
allow["yes - user2 and namespace12] {
check_namespace
check_user
}
.
## main.rego
package system
import data.kubernetes.admission
main = {
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": response,
}
default uid = ""
uid = input.request.uid
response = {
"allowed": true,
"uid": uid,
} {
reason = concat(", ", admission.allow)
reason != ""
}
else = {"allowed": false, "uid": uid}
.
## example input
{
"apiVersion": "admission.k8s.io/v1beta1",
"kind": "AdmissionReview",
"request": {
"namespace": "namespace1",
"userInfo": {
"username": "user2"
}
}
}
.
## Results
"allow": [
"yes - user1 and namespace1",
"yes - user2 and namespace2"
]
It seems that all of my policies are being evaluated as just one flat file, but i would expect that each policy will be evaluated independently from the others
What am I missing here?
Files don't really mean anything to OPA, but packages do. Since both of your policies are defined in the kubernetes.admission module, they'll essentially be appended together as one. This works in your case only due to one of the check_user and check_namespace respectively evaluating to undefined given your input. If they hadn't you would see an error message about conflict, since complete rules can't evalutate to different results (i.e. allow can't be both true and false).
If you rather use a separate package per policy, like, say kubernetes.admission.policy1 and kubernetes.admission.policy2, this would not be a concern. You'd need to update your main policy to collect an aggregate of the allow rules from all of your policies though. Something like:
reason = concat(", ", [a | a := data.kubernetes.admission[policy].allow[_]])
This would iterate over all the sub-packages in kubernetes.admission and collect the allow rule result from each. This pattern is called dynamic policy composition, and I wrote a longer text on the topic here.
(As a side note, you probably want to aggregate deny rules rather than allow. As far as I know, clients like kubectl won't print out the reason from the response unless it's actually denied... and it's generally less useful to know why something succeeded rather than failed. You'll still have the OPA decision logs to consult if you want to know more about why a request succeeded or failed later).

does fabric8 and kubernetes 1.2.4 not support watches on replication controllers ? (code that worked in 1.1 no longer works)

We are trying to upgrade from k8s 1.1 to 1.2.4 and the code that we have for creating watches on replication controllers
seems to no longer work: the watches get created but we received no 'Added' or 'Modified' events for them.
I have created the smallest reproducible case I can and am including it below. For the convenience of
anyone interesting in looking at this I am also including scripts to startup K8s 1.1 and k8s 1.2.4.
Please note the following about the test case:
0. We use fabric8 1.3.91 to connect to k8s 1.2.4 and fabric8 1.2.2 to connect to k8s 1.1.
1. There are slight changes in the fabric8 API that will require you to
tweak the REPRODUCIBLE TEST CASE accoding to the version you run on.
Please run the k8s 1.1 scenario first.
As-shipped, the test program will compile against that version.
After that, please tweak as follows:
comment out the following for new fabric8
import io.fabric8.kubernetes.client.DefaultKubernetesClient.ConfigBuilder
replace masterUrl with: withMasterUrl
comment out the onClose method of new Watcher { ... }
2. you should start the appropriate the appropriate single node k8s script before you run the test program.
3. make sure you create the namespace 'junk6', which you can do by saving
the lines below in a file ns.yaml, then typing kubectl create -f ns.yaml
kind: Namespace
apiVersion: v1
metadata:
name: junk6
labels:
name: junk6
4. For k8s 1.1 you will see a log message containing the string REPCONTROLLER, and the message
future is done... You will not see these when you run under k8s 1.2.4 because it seems
the watch message is never received.
5. The name of the rep controller is spark master, but the image is redis. Please ignore that.
REPRODUCIBLE TEST CASE
package com.foo.blah
import com.fasterxml.jackson.databind.ObjectMapper
import com.typesafe.scalalogging.StrictLogging
import io.fabric8.kubernetes.api.model.ReplicationController
import io.fabric8.kubernetes.client.DefaultKubernetesClient.ConfigBuilder
//import io.fabric8.kubernetes.client.DefaultKubernetesClient.ConfigBuilder /* comment this out for new fabric8 */
import io.fabric8.kubernetes.client.Watcher.Action
import io.fabric8.kubernetes.client._
import scala.collection.JavaConverters._
import scala.concurrent.{Future, Promise}
import scala.util.Try
// To run:
// make sure you have a namespace called junk5 !
object BugReport extends App with StrictLogging {
class RepControllerAndWatchCreator(val rc: io.fabric8.kubernetes.api.model.ReplicationController,
namespace: String) {
def instantiate(kube: KubernetesClient, name: String): Future[Unit] = {
{
val promise = Promise[Unit]()
logger.debug(s"setting up 'create complete' watch for component $name ")
kube.replicationControllers().inNamespace(namespace).create(rc) /* create the rc ! */
val rcWatcher = getRcWatch(name, namespace, promise)
val rcWatch = kube.replicationControllers().inNamespace(namespace).withName(name).watch(rcWatcher)
logger.debug(s"after rc watch - name $name")
promise.future
}
}
private[this] def getRcWatch(name: String,
nameSpace: String,
promise: Promise[Unit]): Watcher[ReplicationController] = {
logger.debug(s"setting up 'create complete' watch for component $name ns=$namespace")
new Watcher[ReplicationController]() {
def eventReceived(action: Action, watchedRc: ReplicationController) {
logger.debug(s"event recv'd for REPCONTROLLER $name. action=$action [ $watchedRc ] ")
promise.success()
}
/* Uncomment this for newer version of fabric8 API.
override def onClose(cause: KubernetesClientException): Unit = {
if (!promise.isCompleted) {
logger.trace("Watcher is close but promise is not completed.")
}
}
*/
}
}
private[this] def isRcComplete(rc: ReplicationController) = {
val retval = rc.getSpec.getReplicas == rc.getStatus.getReplicas
logger.debug(s"isRcComplete [ ${rc.getMetadata.getName} ] = $retval")
retval
}
}
val k8sUrl = "http://localhost:8080"
val namespaceName = "junk6"
def go(): Unit = {
import scala.concurrent.ExecutionContext.Implicits.global
val kube: KubernetesClient = getConnection
val rc: ReplicationController = getRc
val result: Future[Unit] = new RepControllerAndWatchCreator(rc, namespaceName). instantiate(kube ,"spark-master-rc")
result onComplete { (fut: Try[Unit]) =>
println(s"future is done: $fut")
}
Thread.sleep(500 * 1000)
}
def getRc: ReplicationController = {
val jsonTemplate =
"""
|{
| "kind": "ReplicationController",
| "apiVersion": "v1",
| "metadata": {
| "name": "spark-master-rc",
| "labels": {
| "name": "spark-master"
| }
| },
| "spec": {
| "replicas": 1,
| "selector": {
| "name": "spark-master"
| },
| "template": {
| "metadata": {
| "labels": {
| "name": "spark-master"
| }
| },
| "spec": {
| "containers": [
| {
| "name": "spark-master",
| "image": "redis",
| "imagePullPolicy": "Always",
| "ports": [
| {
| "containerPort": 7077
| },
| {
| "containerPort": 8080
| }
| ],
| "resources": {
| "resources": {
| "cpu": "2000m",
| "memory": "4Gi"
| },
| "limits": {
| "cpu": "2000m",
| "memory": "4Gi"
| }
| }
| }
| ]
| }
| }
| }
|}
""".
stripMargin
System.out.println("json:" + jsonTemplate);
new ObjectMapper().readValue(jsonTemplate, classOf[ReplicationController])
}
def getConnection = {
//val configBuilder = new ConfigBuilder() /* For newer fabric8, replace with: Config.builder() */
val configBuilder = new ConfigBuilder() /* For newer fabric8, replace with: Config.builder() */
val config =
configBuilder.
//masterUrl(k8sUrl). /* For newer fabric8, replace with: withMasterUrl */
//withMasterUrl(k8sUrl). /* For older fabric8, replace with: masterUrl */
masterUrl(k8sUrl). /* For newer fabric8, replace with: withMasterUrl */
build()
new DefaultKubernetesClient(config)
}
go()
}
STARTUP SCRIPT FOR K8s 1.1
#!/usr/bin/env bash
# magic selinux context set command is required. for details, see: http://stackoverflow.com/questions/34777111/cannot-create-a-shared-volume-mount-via-emptydir-on-single-node-kubernetes-on
#
sudo chcon -Rt svirt_sandbox_file_t /var/lib/kubelet
docker run --net=host -d gcr.io/google_containers/etcd:2.0.12 /usr/local/bin/etcd --addr=127.0.0.1:4001 --bind-addr=0.0.0.0:4001 --data-dir=/var/etcd/data
docker run \
--volume=/:/rootfs:ro \
--volume=/sys:/sys:ro \
--volume=/dev:/dev \
--vol
STARTUP SCRIPT FOR K8s 1.2.4
#!/usr/bin/env bash
# magic selinux context set command is required. for details, see: http://stackoverflow.com/questions/34777111/cannot-create-a-shared-volume-mount-via-emptydir-on-single-node-kubernetes-on
#
sudo chcon -Rt svirt_sandbox_file_t /var/lib/kubelet
#docker run --net=host -d gcr.io/google_containers/etcd:2.0.12 /usr/local/bin/etcd --addr=127.0.0.1:4001 --bind-addr=0.0.0.0:4001 --data-dir=/var/etcd/data
# Update k8s local cluster to latest stable version according to "Running Kubernetes Locally via Docker"
# http://kubernetes.io/docs/getting-started-guides/docker/
# export K8S_VERSION=$(curl -sS https://storage.googleapis.com/kubernetes-release/release/stable.txt)
export K8S_VERSION=v1.2.4
export ARCH=amd64
docker run \
--volume=/:/rootfs:ro \
--volume=/sys:/sys:ro \
--volume=/dev:/dev \
--volume=/var/lib/docker/:/var/lib/docker:ro \
--volume=/var/lib/kubelet/:/var/lib/kubelet:rw \
--volume=/var/run:/var/run:rw \
--net=host \
--pid=host \
--privileged=true \
-d \
gcr.io/google_containers/hyperkube-${ARCH}:${K8S_VERSION} \
/hyperkube kubelet \
--containerized \
--hostname-override="127.0.0.1" \
--address="0.0.0.0" \
--api-servers=http://localhost:8080 \
--config=/etc/kubernetes/manifests \
--allow-privileged --v=2
#docker run -d --net=host --privileged gcr.io/google_containers/hyperkube:v1.0.1 /hyperkube proxy --master=http://127.0.0.1:8080 --v=2
sleep 5 # give everything time to launch
So the answer, from my colleague Vijay is expressed in the verification program below.
The key issue (also mentioned in this bug report to fabric8 folks) is that order of creation of object and watch on the object matters.
Thanks, Vijay !
To summarize the bug report:
if you switched the order of these statements
client.replicationControllers().inNamespace(namespace).withLabel("l", "v").watch(watcher);
createRc(client, namespace, podName, image);
to this:
createRc(client, namespace, podName, image);
client.replicationControllers().inNamespace(namespace).withLabel("l", "v").watch(watcher);
```
the program would cease to work. Switching the order would have been fine in 1.2.2 as far as i can tell from the testing i have done.
** Vijays Solution **
import com.fasterxml.jackson.databind.ObjectMapper;
import com.typesafe.scalalogging.StrictLogging;
import io.fabric8.kubernetes.api.model.Quantity;
import io.fabric8.kubernetes.api.model.ReplicationController;
import io.fabric8.kubernetes.client.Watcher.Action;
import io.fabric8.kubernetes.client.*;
import java.util.HashMap;
import java.util.Map;
public class Vijay {
public static DefaultKubernetesClient getConnection () {
ConfigBuilder
configBuilder = Config.builder() ;
Config config =
configBuilder.
withMasterUrl("http://localhost:8080").
build();
return new DefaultKubernetesClient(config);
}
public static void main(String[] args) throws Exception {
DefaultKubernetesClient client = getConnection();
String namespace = "junk6";
String podName = "prom";
String image = "nginx";
Watcher<ReplicationController> watcher = new Watcher<ReplicationController>() {
#Override
public void onClose(KubernetesClientException cause) {
// TODO Auto-generated method stub
}
#Override
public void eventReceived(Action action, ReplicationController resource) {
System.out.println(action + ":" + resource);
}
};
client.replicationControllers().inNamespace(namespace).withLabel("l", "v").watch(watcher);
createRc(client, namespace, podName, image);
}
private static void createRc(DefaultKubernetesClient client, String namespace, String podName, String image) {
try {
Map<String, String> labels = new HashMap<String, String>();
labels.put("l", "v");
ReplicationController rc = client.replicationControllers().inNamespace(namespace)
.createNew()
.withNewMetadata()
.withName(podName)
.addToLabels(labels)
.endMetadata()
.withNewSpec()
.withReplicas(1)
.withSelector(labels)
.withNewTemplate()
.withNewMetadata()
.addToLabels(labels)
.endMetadata()
.withNewSpec()
.addNewContainer()
.withName(podName)
.withImage(image)
.withImagePullPolicy("Always")
.withNewResources()
.addToLimits("cpu", new Quantity("100m"))
.addToLimits("memory", new Quantity("100Mi"))
.endResources()
.endContainer()
.endSpec()
.endTemplate()
.endSpec()
.done();
} catch (Exception e) {
e.printStackTrace();
}
}
}