Getting a letsencrypt SSL certificate for the OpenShift console and API

By default when you install an OpenShift install it would automatically generate its own certificates.

It uses those certificates for communication between nodes and as well to automatically auth the admin account. By default those same certificates are the one provided for the OpenShift console and API.

Since it is auto generated you will when connecting  to the website with you webbrowser get an ugly error message :

2016-09-28__23-40-01-20126

 

 

and as the error message says that’s not very secure #sadpanda.

There is an easy way to generate certificate these days and it is to use letsencrypt, so let’s see how to connect it to the openshift console.

There is something to understand first here,  when you want to use an alternate SSL certificates for your console and API you can’t do that on your default (master) URL, it has to be another url. Phrased in another way here is a quote from the official documentation  :

2016-09-28__23-55-03-27531

with that in mind let’s assume you have setup a domain being a CNAME to your default domain. For myself here since this is a test install I went to use the easy way and I will use the xp.io service as I have documented in an earlier post. This give me easily a domain which would look like this :

lb.198.154.189.125.xip.io

So now that you have defined it, you need first to generate the letsencrypt certificate usually you would use certbot from RHEL EPEL to generate them but unfortunately at the time of writing this blog post the package was  uninstallable for me which probably would get fixed soon. In the meantime I have used letsencrypt from git directly as like this:

$ git clone https://github.com/letsencrypt/letsencrypt

before you do anything, you need to understand the letsencrypt  process, usually you would have an apache or nginx (etc…) serving the generated files for verifications  (the /.well-known/ thing) since we can’t do that for us in openshift you can use the letsencrypt builtin webserver for that.

But to start the builtin webserver you need to be able to do it to bind it on port 80  but for us on master there is the router running which bind to it (and 443), so you would need to make sure it’s down and the most elegant way to do that with openshift is like this :

$ oc scale –replicas=0 dc router

now that you have nothing on port 80 you can tell letsencrypt to do its magic with this command line :

$ ./letsencrypt-auto –renew-by-default -a standalone –webroot-path /tmp/letsencrypt/ –server https://acme-v01.api.letsencrypt.org/directory –email email@email.com –text –agree-tos –agree-dev-preview -d lb.198.154.189.125.xip.io auth

change the lb.198.154.189.125.xip.io here to your own domain as the email address, if everything goes well you should get something like this :

2016-09-29__00-08-22-10578

now you should have all the certificates needed in /etc/letsencrypt/live/${domain}

So there is a little caveat here, there is a bug in openshift-ansible currently with symlinks and certificates and the way it operates. I have filled the bug here and it has already been fixed in GIT so hopefully by the time you will read this article this would be fixed in the openshift-ansible rpm if it’s not you can directly use the GIT openshift-ansible instead of the package.mber (3) here is going to change so you would have to adjust.

now you just need to some configuration in your /etc/ansible/hosts file :

openshift_master_cluster_public_hostname=lb.198.154.189.125.xip.io
openshift_master_named_certificates=[{"certfile": "/etc/letsencrypt/live/lb.198.154.189.125.xip.io/full.pem", "keyfile": "/etc/letsencrypt/live/lb.198.154.189.125.xip.io/privkey.pem", "names":["lb.198.154.189.125.xip.io"]}]
openshift_master_overwrite_named_certificates=true

after you run your playbook (with ansible-playbook /usr/share/ansible/openshift-ansible/playbooks/byo/config.yml) you should have it running properly and now when accessing by the console you should the reassuring secure lock :

2016-09-29__10-11-32-12477

NB:

  • If you need to renew the certs just do the steps where you oc scale the router quickly and renew the certificate with the letsencrypt auto command line mentioned earlier.
  • There is probably a way more elegant way to do that with a container and a route. I saw this on dockerhub but this seems to be tailored to apps (and kube) and I don’t think this could be used for the OpenShift console.
  • Don’t forget to oc scale –replicas=1 dc/router (even tho the ansible rerun should have done for you.

Easily test your OpenShift applications exposed by the router

OpenShift integrate[1] a router based on HAproxy to expose your services to the outside world. Whenever your do a :

oc expose servicename

it would expose by default the servicename this URL :

servicenameprojectname.defaultSubDomain

The defaultSubdomain is usually a wildcard DNS record that you have configured in your domain server by your system administrator. 

Now for your openshift testing if you don’t want to ask your system administrator to configure a new CNAME going to your testing environement you can just use the free service xp.io

The XP.IO service is a special DNS service which would take a an IP address and xp.io and report back the IP of this IP address to itself and to all subdomain so that the IP:

blah.1.2.3.4.xp.io

will go to 1.2.3.4 same goes for foo.1.2.3.4, bar.1.2.3.4 etc…

You just then need to configure it in OpenShift by editing the value (assuming 1.2.3.4 is your public IP which come back to your router) :


routingConfig:
    subdomain: "1.2.3.4.xip.io"

Or if you use the openshift-ansible scripts to add this your /etc/ansible/hosts

osm_default_subdomain=1.2.3.4.xip.io

and then you get all your route exposed properly without bother your always busy system admin.

[1] Which lately got merged into kubernetes as the “ingress” feature

How to view openshift router (haproxy) stats

After you have installed your fancy openshift install and that it kicked the haproxy router automatically after install you may want to see the stats of the router.

The HAproxy stats are exposed on the port 1936 where the router is located (usually on the master node) so first you need a way to access it. You can open it via your firewall (not ideal) or you can just port forward the port to your workstation via SSH :

$ ssh -L 1936:localhost:1936 master.openshift

Now that it’s done and you have 1936 tunelled you need to figure out the password of the haproxy stats. It’s stored in its environment variables so you just do a oc describe to see it for example :

2016-09-27__12-58-57-15400

Now that you have the password (uo5LtC6mac in my case), you just point your workstation web browser to :

http://admin:password@localhost:1936

just make sure to replace the password with your own password and you should be all set.

2016-09-27__13-01-20-4942

Using python to drive OpenShift REST API

I have been meaning to automate my deployment directly from my small python application without having to use the openshift client (oc) directly.

OpenShift use a REST API and the oc client uses it to communicate with the server, you can actually see all the REST operation the oc client is doing if you specify the –loglevel=7 (it goes to 10 to get even more debug info) :

$ oc --loglevel=7 get pod 2>&1 |head -10
I0919 09:59:20.047350   77328 loader.go:329] Config loaded from file /Users/chmouel/.kube/config
I0919 09:59:20.048149   77328 round_trippers.go:296] GET https://openshift:8443/oapi
I0919 09:59:20.048158   77328 round_trippers.go:303] Request Headers:
I0919 09:59:20.048162   77328 round_trippers.go:306]     User-Agent: oc/v1.4.0 (darwin/amd64) openshift/85eb37b
I0919 09:59:20.048175   77328 round_trippers.go:306]     Authorization: Bearer FOOBAR
I0919 09:59:20.048180   77328 round_trippers.go:306]     Accept: application/json, */*
I0919 09:59:20.095239   77328 round_trippers.go:321] Response Status: 200 OK in 47 milliseconds
I0919 09:59:20.096056   77328 round_trippers.go:296] GET https://openshift:8443/version
I0919 09:59:20.096078   77328 round_trippers.go:303] Request Headers:
I0919 09:59:20.096084   77328 round_trippers.go:306]     User-Agent: oc/v1.4.0 (darwin/amd64) openshift/85eb37b

I was thinking to come up with my own python rest wrapper since a google quick search didn’t come up with any binding. But since openshift is build on kubernetes and fully compatible with it (i.e: no fork or changes that make it incompatible) it was as easy as using the tools provided for kube.

The first project coming up on the google search is pykube and it’s easily installable with pip.

You need to provide a kubeconfig that was already setup (with username/passwd) or already identified if it’s token based (i.e: oauth, oid etc) and you can use this example like this :


import pykube
api = pykube.HTTPClient(pykube.KubeConfig.from_file("/Users/chmouel/.kube/config"))
pods = pykube.Pod.objects(api).filter(namespace="test")
for x in pods:
    print(x)

see the documentation of pykub on its website

Getting openshift origin “cluster up” working with xhyve

In latest openshift client (oc) there is a nifty (relatively) new feature to get you a OpenShift cluster starting (very) quickly. It’s a pretty nice way to get you a new openshift origin environment on your laptop  without the hassle.

On macosx there is a (as well relatively) new lightweight virtualization solution called xhyve it’s a bit like KVM in the sense of being lightweight and does not need like virtualbox or vmware to get a UI running. It seemed to be a perfect fit to try those two together.

xhyve docker machine driver needed to be installed first so I just went on its website here :

https://github.com/zchee/docker-machine-driver-xhyve

and followed the installation instruction from the README which I could see everything was working :

2016-09-18__21-27-28-10262

I then fired up the “oc cluster up –create-machine” command and to my disappointment it was starting by default the virtualbox and I could not see anything in the options how to specify the “–driver xhyve” option to docker-machine which is what the oc cluster feature is using on the backend to bootstrap a docker environment.

Digging into the code it seems that the oc cluster has those feature set in static as virtualbox :

https://github.com/openshift/origin/blob/85eb37b34f0657631592356d020cef5a58470f8e/pkg/bootstrap/docker/dockermachine/helper.go#L56-L79

since there was no way to pass other options I first looked in the github issues to see if there was nothing reported about it and sent a feature request here,

I started to think a little bit more about a workaround going from modifying to my liking and recompiling the oc client or to just give up on xhyve but in fact the solution is actually much simplier.

Since there is the ability to specify to “oc cluster up” an already configured docker-machine environment with the ” –docker-machine” option. We just had to configured previously properly first (which is with the option –engine-insecure-registry 172.30.0.0/16) :

2016-09-18__21-05-12-14647

and after a bit the new docker should be setup which can be easily used with the command eval $(docker-machine env xhyve)

I then just have to start my oc cluster up with the option  –docker-machine=”xhyve” and I would get my nicely setuped openshift origin cluster to play with in mere seconds :

2016-09-18__21-04-47-3802

Dealing with yaml in Emacs

Sometime ago or at least when I started doing programming in the late 90s XML was all the rage, it promised to be the panacea for everything from data to storage to data presentation and processing. People realised that it was just complexity as Joel Spolski points out an attempt to make the complex seem accessible to ordinary people.. Really people were annoyed to write all those tags as those ‘<‘ and ‘>’ are hard to reach on a qwerty keyboard.

Beginning the new millennia in 2000 the web started to get very popular and things like “web services” popped up everywhere, people realised that actually XML is not that great so started to get a format called Json to get computers talking to each others in a sane manner.

But people realise that json was actually not that great to chat between web services as it was actually designed to serialize objects between programming languages. And really down the line it’s more about the programmers being annoyed by all those { } [ ] brackets

So here came yaml the latest “fashion format” based on the popularity of tab based programming languages.

Most new software lately have been using it, all the containers software ecosystem configure things in yaml so you have to deal with it when you work with them.

I don’t know if I like yaml or not, the only thing i know is that when I have a big ass large yaml file it become quickly unreadable. You have no idea which blocks belong to which one and not sure how many indents you need to add to that block to align to that other one that started 800 lines ago.

This has been driving me crazy as I need to write some large kubernetes/OpenShift yaml files and sometime end up for hours trying to detect where I have my tab alignment.

Some may argue, but you do python and python is tab based. Yeah i have been doing python for the last 10 years and this has never been issue cause first I don’t write kick ass 5000 lines python functions and second the python mode of my editor Emacs is properly configured.

Ah there I say it, the editor needs to be configured properly to have a good workflow so here is Emacs to the rescue to make it bearable (and make that post more productive that another rant from the interweb)

So without further ado and with much fanfare, here is the emacs extension i found to make writing yaml bearable :

Highlight Indentation for Emacs

2016-09-07__09-06-21-543

This mode would give you a visual representation of the current indentation with a bar showing the indentation.

Smart Shift

Make Shift

This mode doesn’t give you a visual but allow you to indent blocks of texts easily. Usually in emacs you would use the Control-C Tab command to indent and prefix it with a number for the number of indent. For example C-u 4 Control-C Tab would indent the text for 4 spaces. Smart shift make things much easier to move around.

Flycheck-mode

Flycheck mode

This is a generics mode you should really configure for all your programming needs, it supports yaml files and will try to validate (with ruby-yaml library) your yaml file and see where you have an error.

aj-toggle-fold

2016-09-07__09-36-55-32078

This is a function I found in a post on stackoverflow (by the author of Highlight-Indentation-for-Emacs) it allow you to folds all code on an indentation level greater than the current line. A great way to show you the current outline of the file.

 

openshift-sdn with OpenStack SDN and MTU

I am lucky enough to have a cloud available to me for free it obviously runs OpenStack and I can kick VM like I want.

Since I am playing with OpenShift a lot lately I have seen issues in that cloud where pushing an image to the internal registry was just randomly failing.

Networking is definitely not my pedigree but I could definitely sense it was a networking issue. Since I could nost just blame the underlying cloud (hey it’s free!) I had to investigate a bit.

Using the “access to internal docker registry” feature of OpenShift, I could definitively push from the master (where the registry was) in 2s but not from the node where it was completely stucks at the end while it could only push some bits at first and after waiting forever there.

I came back to our internal mailing list and the local experts there pointed me to the file :

/etc/sysconfig/openshift-node

and the interesting part is this :

# The $DOCKER_NETWORK_OPTIONS variable is used by sdn plugins to set
# $DOCKER_NETWORK_OPTIONS variable in the /etc/sysconfig/docker-network
# Most plugins include their own defaults within the scripts
# TODO: More elegant solution like this
# https://github.com/coreos/flannel/blob/master/dist/mk-docker-opts.sh
# DOCKER_NETWORK_OPTIONS='-b=lbr0 --mtu=1450'

I uncommented and adjusted my MTU to 1400 since 1450 wasn’t working for me and after a reboot I could push properly my images from the nodes to the internal registry.

Thanks to sdodson and Erik for pointing me to this

Deploy openshift router and registry only on a master nodes with no others

Something that has come up when using OpenShift and that was tricky enough to be shared on a blog post.

On OpenShift you have this routers  and registry which by default are on the master nodes and that’s fine. Things get tricky if you don’t want anything else in there.

I finally figured this out after digging in some internal mailing lists and this is actually not too difficult. The key thing is to have this on the ‘default‘ namespace annotations :

openshift.io/node-selector: region=infra

The default namespace is an internal namespace used for openshift infrastructure services.

Let me describe this a little bit further, here is my node labels configuration :

root@master:~$ oc get node
NAME                                 LABELS                                                                                STATUS    AGE
master.local.openshift.chmouel.com   kubernetes.io/hostname=master.local.openshift.chmouel.com,region=infra,zone=default   Ready     2d
node1.local.openshift.chmouel.com    kubernetes.io/hostname=node1.local.openshift.chmouel.com,region=primary,zone=west     Ready     2d
node2.local.openshift.chmouel.com    kubernetes.io/hostname=node2.local.openshift.chmouel.com,region=primary,zone=east     Ready     2d

I had already a router running fine on my master by forcing (this was generated by the oadm router command) it with a nodeSelector on the deploymentConfig :

root@master:~$ oc get pod router-1-q3am8 -o yaml
[..]
nodeName: master.local.openshift.chmouel.com
nodeSelector:
region: infra
[..]

Now I am going to edit my /etc/origin/master/master-config.yaml and add :

projectConfig:
    defaultNodeSelector: "region=primary"

which force all new nodes to get on the primary region.

As expected if I delete my router and redeploy it :

root@master:~$ oc delete pod router-1-q3am8
root@master:~$ oc deploy router --latest

The router was not able to be deployed since getting since we explicitely told the scheduler that we want pods only on infra :

Sep 23 09:45:52 master.local.openshift.chmouel.com origin-master[2879]: I0923 09:45:52.203596 2879 event.go:203] Event(api.ObjectReference{Kind:"ReplicationController", Namespace:"default", Name:"router-1", UID:"454f46b0-5fbc-11e5-9c22-fa163e93ac32", APIVersion:"v1", ResourceVersion:"99201", FieldPath:""}): reason: 'failedCreate' Error creating: pods "" is forbidden: pod node label selector conflicts with its project node label selector

So what I had to do now is to edit the default namespace (not project but namespace that’s a critical point) and add in the metadata/annotations section :

apiVersion: v1
kind: Namespace
metadata:
   annotations:
      openshift.io/node-selector: region=infra

which to say that the default project can be indeed deployed on region=infra.

Now let’s try again :

root@master:~$ oc deploy router --latest

and check the log :

Sep 23 09:47:25 master.local.openshift.chmouel.com origin-master[2879]: I0923 09:47:25.341257 2879 event.go:203] Event(api.ObjectReference{Kind:"ReplicationController", Namespace:"default", Name:"router-1", UID:"454f46b0-5fbc-11e5-9c22-fa163e93ac32", APIVersion:"v1", ResourceVersion:"99201", FieldPath:""}): reason: 'successfulCreate' Created pod: router-1-l5r0e

which seems to work fine and deployed on infra :

root@master:~$ oc get pod|grep router
router-1-ed6dk            1/1       Running   0          1h
root@master:~$

Using yaml for OpenShift v3 templates

I have been experimenting a lot with OpenShift v3 and love how everything work well together plugging Kubernetes and Docker with a PAAS workflow.

One of the thing that I don’t get is to have to write manually verbose json templates, it’s wonderful for machines and to parse but writing it can get as painful as (dear I said it) XML.

OpenShift natively support very nicely yaml files and it’s a straight conversion of what you would have in json format.

Since at this time most of the examples are in json I wrote a script to quickly convert them to yaml and came up with this command line using python and bash :

for i in $(find . -name '*.json');do  python -c 'import sys,json,yaml;print(yaml.safe_dump(json.loads(sys.stdin.read()), default_flow_style=False))' < $i > ${i/json/yaml};done

Happy Yameling (I just made this word up and I am not even drunk)