10/03/2018

Image-charts evolution + my first book

It's been a while since my last blog post, I've been working hard on improving Image-Charts these past months as well as structuring my professional (at Ouest-France) and personal life with GTD.

I'm starting to share numbers about my SaaS, so without further ado, here are the last reports for Image-Charts 🦄

Oh, one last thing 😅📚! I started to write a book about (temporary title) "everything I wish I had known before being a CTO" "From a small SaaS with huge growth to a large corporation"

It's a collection of every principle and patterns I've discovered since 2012 from organization specification, internal process scaling, resource/staffing, management, operations, choices, with of course a large part on the technical-side and vision. These past years a lot of colleagues asked me to gather them in a single place so here we are! I wish to complete it before my 30th birthday (just for fun), I've still got 9 months to go and at that point gathered the main principles I repeat at work everyday.

Subscribe below to be notified when it's published 🐣!

6/19/2018

How I saved 600€/year on Redsmin building a reusable payment micro-service

Original post on blog.redsmin.com

Like any SaaS business, Redsmin has to manage customer payments. To that end, I always use Stripe, an online payment gateway with an awesome developer experience (DX).

1 year after Redsmin’s initial launch, back in December 2014, I also subscribed to Stunning to manage pre-dunning, dunning email and easily set up a page allowing my clients to update their payment details.

However, when I took a look at my fixed expenses over the past 4 years, I noticed that a nonanecdotal part of our fixed expenses were directly allocated to Stunning. As I launch almost one new SaaS business every year, and that Image-Charts’ customers were already asking me to update their credit card information, I had to take action. Either buy another Stunning account or do-what-I-always-do: build from scratch a service that handles it for me… Thus migrating Redsmin from Stunning to this new service written from scratch.

That's how the idea to build a reusable piece of software came to mind: Stripe-update-card was born.

As I'm progressively moving my stack from NodeJS ecosystem to Rust, I had to fix some issues on stripe-rs library. Besides this, everything went fine and the service has been written and deployed on CleverCloud in a day (awesome service, give it a try!). Stripe-update-card on Github, PRs are welcome!

I wanted this microservice to be very simple yet fully customizable to be usable inin many products. It's already used in production at Redsmin, Image-Charts and will be deployed in the coming months on KillBug and GetSignature (but that’s another story).

Stripe-update-card’s form is fully customizable, only 3 environment variables are required to start the service (STRIPE_PUBLISHABLE_KEY, STRIPE_SECRET_KEY and SUCCESS_REDIRECT_URL). You can use the others to better promote your brand, product description, logo and so on.

docker run -it \
 -e STRIPE_PUBLISHABLE_KEY=pk_test_xxx \
 -e STRIPE_SECRET_KEY=sk_test_xxx \
 -e SUCCESS_REDIRECT_URL=https://url.to.redirect/on/success \
 -p 8080:8080 \
 fgribreau/stripe-update-card

I'm pretty sure other indie/SaaS hackers will find it useful (instead of rewriting it from scratch every time). If so, don't hesitate to reach out and please send a PR to update "Running in production" README section!

5/03/2018

Pretty print Kubernetes events (kubectl get events -w)

Kubernetes events stream with kubectl get events --all-namespaces --watch are very hard to read, unless you got a very very (very) large screen.

NAMESPACE      LAST SEEN                        FIRST SEEN                       COUNT     NAME                                                        KIND                      SUBOBJECT                              TYPE      REASON                  SOURCE                                                         MESSAGE
image-charts   2018-05-03 20:36:08 +0200 CEST   2018-05-03 20:36:08 +0200 CEST   1         image-charts-deployment-57575d876f-5vjvj.152b364506573f50   Pod                                                              Normal    Scheduled               default-scheduler                                              Successfully assigned image-charts-deployment-57575d876f-5vjvj to gke-image-charts-us-w-production-pool-09fa8042-gmcd
image-charts   2018-05-03 20:36:09 +0200 CEST   2018-05-03 20:36:09 +0200 CEST   1         image-charts-deployment-57575d876f-5vjvj.152b36451cb11f32   Pod                                                              Normal    SuccessfulMountVolume   kubelet, gke-image-charts-us-w-production-pool-09fa8042-gmcd   MountVolume.SetUp succeeded for volume "default-token-psj7t"
image-charts   2018-05-03 20:36:11 +0200 CEST   2018-05-03 20:36:11 +0200 CEST   1         image-charts-deployment-57575d876f-5vjvj.152b3645963079f5   Pod                       spec.containers{image-charts-runner}   Normal    Pulled                  kubelet, gke-image-charts-us-w-production-pool-09fa8042-gmcd   Container image "eu.gcr.io/ic-18/image-charts:master-ef08ceeaf315c89f7e7dd4bc4cec8f9b0cef834a" already present on machine
image-charts   2018-05-03 20:36:11 +0200 CEST   2018-05-03 20:36:11 +0200 CEST   1         image-charts-deployment-57575d876f-5vjvj.152b364599f0352e   Pod                       spec.containers{image-charts-runner}   Normal    Created                 kubelet, gke-image-charts-us-w-production-pool-09fa8042-gmcd   Created container
image-charts   2018-05-03 20:36:12 +0200 CEST   2018-05-03 20:36:12 +0200 CEST   1         image-charts-deployment-57575d876f-5vjvj.152b3645c253b80f   Pod                       spec.containers{image-charts-runner}   Normal    Started                 kubelet, gke-image-charts-us-w-production-pool-09fa8042-gmcd   Started container
image-charts   2018-05-03 20:42:08 +0200 CEST   2018-05-03 20:42:08 +0200 CEST   1         image-charts-deployment-57575d876f-5vjvj.152b3698d837ff3c   Pod                       spec.containers{image-charts-runner}   Normal    Killing                 kubelet, gke-image-charts-us-w-production-pool-09fa8042-gmcd   Killing container with id docker://image-charts-runner:Need to kill Pod
image-charts   2018-05-03 20:20:38 +0200 CEST   2018-05-03 20:20:38 +0200 CEST   1         image-charts-deployment-57575d876f-v74c8.152b356c77a29f53   Pod                                                              Normal    Scheduled               default-scheduler                                              Successfully assigned image-charts-deployment-57575d876f-v74c8 to gke-image-charts-us-w-production-pool-09fa8042-gmcd
image-charts   2018-05-03 20:20:39 +0200 CEST   2018-05-03 20:20:39 +0200 CEST   1         image-charts-deployment-57575d876f-v74c8.152b356c88794874   Pod                                                              Normal    SuccessfulMountVolume   kubelet, gke-image-charts-us-w-production-pool-09fa8042-gmcd   MountVolume.SetUp succeeded for volume "default-token-psj7t"
image-charts   2018-05-03 20:20:40 +0200 CEST   2018-05-03 20:20:40 +0200 CEST   1         image-charts-deployment-57575d876f-v74c8.152b356ce43ac4e3   Pod                       spec.containers{image-charts-runner}   Normal    Pulled                  kubelet, gke-image-charts-us-w-production-pool-09fa8042-gmcd   Container image "eu.gcr.io/ic-18/image-charts:master-ef08ceeaf315c89f7e7dd4bc4cec8f9b0cef834a" already present on machine
image-charts   2018-05-03 20:20:40 +0200 CEST   2018-05-03 20:20:40 +0200 CEST   1         image-charts-deployment-57575d876f-v74c8.152b356ceadd2e40   Pod                       spec.containers{image-charts-runner}   Normal    Created                 kubelet, gke-image-charts-us-w-production-pool-09fa8042-gmcd   Created container
image-charts   2018-05-03 20:20:41 +0200 CEST   2018-05-03 20:20:41 +0200 CEST   1         image-charts-deployment-57575d876f-v74c8.152b356d1403821b   Pod                       spec.containers{image-charts-runner}   Normal    Started                 kubelet, gke-image-charts-us-w-production-pool-09fa8042-gmcd   Started container
image-charts   2018-05-03 20:26:09 +0200 CEST   2018-05-03 20:26:09 +0200 CEST   1         image-charts-deployment-57575d876f-v74c8.152b35b95bfa97e4   Pod                       spec.containers{image-charts-runner}   Normal    Killing                 kubelet, gke-image-charts-us-w-production-pool-09fa8042-gmcd   Killing container with id docker://image-charts-runner:Need to kill Pod
image-charts   2018-05-03 20:20:38 +0200 CEST   2018-05-03 20:20:38 +0200 CEST   1         image-charts-deployment-57575d876f.152b356c778b2a36         ReplicaSet                                                       Normal    SuccessfulCreate        replicaset-controller                                          Created pod: image-charts-deployment-57575d876f-v74c8
image-charts   2018-05-03 20:26:08 +0200 CEST   2018-05-03 20:26:08 +0200 CEST   1         image-charts-deployment-57575d876f.152b35b94cd06d75         ReplicaSet                                                       Normal    SuccessfulDelete        replicaset-controller                                          Deleted pod: image-charts-deployment-57575d876f-v74c8
image-charts   2018-05-03 20:36:08 +0200 CEST   2018-05-03 20:36:08 +0200 CEST   1         image-charts-deployment-57575d876f.152b364505f8a022         ReplicaSet                                                       Normal    SuccessfulCreate        replicaset-controller                                          Created pod: image-charts-deployment-57575d876f-5vjvj
image-charts   2018-05-03 20:42:08 +0200 CEST   2018-05-03 20:42:08 +0200 CEST   1         image-charts-deployment-57575d876f.152b3698d19cba5f         ReplicaSet                                                       Normal    SuccessfulDelete        replicaset-controller                                          Deleted pod: image-charts-deployment-57575d876f-5vjvj
image-charts   2018-05-03 20:42:08 +0200 CEST   2018-05-03 12:27:10 +0200 CEST   21        image-charts-deployment.152b1b9630769766                    Deployment                                                       Normal    ScalingReplicaSet       deployment-controller                                          Scaled down replica set image-charts-deployment-57575d876f to 3
image-charts   2018-05-03 20:36:08 +0200 CEST   2018-05-03 12:47:08 +0200 CEST   20        image-charts-deployment.152b1cad1828a4bc                    Deployment                                                       Normal    ScalingReplicaSet       deployment-controller                                          Scaled up replica set image-charts-deployment-57575d876f to 4
image-charts   2018-05-03 20:36:08 +0200 CEST   2018-04-23 22:32:29 +0200 CEST   205       image-charts-hpa.15282ad08af5393c                           HorizontalPodAutoscaler                                          Normal    SuccessfulRescale       horizontal-pod-autoscaler                                      New size: 4; reason: cpu resource utilization (percentage of request) above target
image-charts   2018-05-03 20:42:08 +0200 CEST   2018-04-23 22:38:29 +0200 CEST   205       image-charts-hpa.15282b2472554668                           HorizontalPodAutoscaler                                          Normal    SuccessfulRescale       horizontal-pod-autoscaler                                      New size: 3; reason: All metrics below target

Let's specify a go-template and improve this:

kubectl get events --all-namespaces --watch -o 'go-template={{.lastTimestamp}} {{.involvedObject.kind}} {{.message}} ({{.involvedObject.name}}){{"\n"}}'

2018-05-03T18:36:08Z Pod Successfully assigned image-charts-deployment-57575d876f-5vjvj to gke-image-charts-us-w-production-pool-09fa8042-gmcd (image-charts-deployment-57575d876f-5vjvj)
2018-05-03T18:36:09Z Pod MountVolume.SetUp succeeded for volume "default-token-psj7t"  (image-charts-deployment-57575d876f-5vjvj)
2018-05-03T18:36:11Z Pod Container image "eu.gcr.io/killbug-165718/image-charts:master-ef08ceeaf315c89f7e7dd4bc4cec8f9b0cef834a" already present on machine (image-charts-deployment-57575d876f-5vjvj)
2018-05-03T18:36:11Z Pod Created container (image-charts-deployment-57575d876f-5vjvj)
2018-05-03T18:36:12Z Pod Started container (image-charts-deployment-57575d876f-5vjvj)
2018-05-03T18:42:08Z Pod Killing container with id docker://image-charts-runner:Need to kill Pod (image-charts-deployment-57575d876f-5vjvj)
2018-05-03T18:20:38Z Pod Successfully assigned image-charts-deployment-57575d876f-v74c8 to gke-image-charts-us-w-production-pool-09fa8042-gmcd (image-charts-deployment-57575d876f-v74c8)
2018-05-03T18:20:39Z Pod MountVolume.SetUp succeeded for volume "default-token-psj7t"  (image-charts-deployment-57575d876f-v74c8)
2018-05-03T18:20:40Z Pod Container image "eu.gcr.io/killbug-165718/image-charts:master-ef08ceeaf315c89f7e7dd4bc4cec8f9b0cef834a" already present on machine (image-charts-deployment-57575d876f-v74c8)
2018-05-03T18:20:40Z Pod Created container (image-charts-deployment-57575d876f-v74c8)
2018-05-03T18:20:41Z Pod Started container (image-charts-deployment-57575d876f-v74c8)
2018-05-03T18:26:09Z Pod Killing container with id docker://image-charts-runner:Need to kill Pod (image-charts-deployment-57575d876f-v74c8)
2018-05-03T18:20:38Z ReplicaSet Created pod: image-charts-deployment-57575d876f-v74c8 (image-charts-deployment-57575d876f)
2018-05-03T18:26:08Z ReplicaSet Deleted pod: image-charts-deployment-57575d876f-v74c8 (image-charts-deployment-57575d876f)
2018-05-03T18:36:08Z ReplicaSet Created pod: image-charts-deployment-57575d876f-5vjvj (image-charts-deployment-57575d876f)
2018-05-03T18:42:08Z ReplicaSet Deleted pod: image-charts-deployment-57575d876f-5vjvj (image-charts-deployment-57575d876f)
2018-05-03T18:42:08Z Deployment Scaled down replica set image-charts-deployment-57575d876f to 3 (image-charts-deployment)
2018-05-03T18:36:08Z Deployment Scaled up replica set image-charts-deployment-57575d876f to 4 (image-charts-deployment)
2018-05-03T18:36:08Z HorizontalPodAutoscaler New size: 4; reason: cpu resource utilization (percentage of request) above target (image-charts-hpa)
2018-05-03T18:42:08Z HorizontalPodAutoscaler New size: 3; reason: All metrics below target (image-charts-hpa)

Much better right?

Last thing, we do not want to remember this so let's define a function in ~/.zshrc (or equivalent):

kubectl-events() {
  kubectl get events --all-namespaces --watch -o 'go-template={{.lastTimestamp}} {{.involvedObject.kind}} {{.message}} ({{.involvedObject.name}}){{"\n"}}'
}

Done 👍

Note: if you wish to be a Kubernetes power-user take a look at kubectx cli!

Special thanks to @m_pousse that wrote the initial go-template version based on a kubernetes event stream app I was working on!

« »
 
 
Made with on a hot august night from an airplane the 19th of March 2017.