Two years ago, at the beginning of 2020, we wanted to improve the visibility of Michelin's internal IT so we decided to create our technology blog, but we had to choose the platform to host it. This article is a REX on how we chose and implemented our blogging platform to meet our needs.
TL;DR We use GHOST to 'dust off the image of IT in the industry'
Why a Michelin technical blog ?
Michelin is known worldwide for its tires and tire-related services but less for its internal IT strategies / technologies / organization to support end-to-end design to services around tires.
Why launching this blog managed by Michelin IT experts? Our great team :-) wanted to:
- Demystify and share what Michelin does with IT, both at technical and organizational levels.
- Deploy a continuous learning platform to share ideas & and our expertise for innovative technologies, our passion for the next generation of software.
- Give back to the community how we have implemented our solutions based on common components.
Many companies share technical news and achievements of their IT teams;
Below, find a list of some of our great peers sharing their technical insights, experience and useful tips.
Our platform Choice
We wanted a straightforward platform to share blog posts and pages with a community.
Our decision drivers were:
- Worldwide availability without restriction
- Easy to use
- Moderation feature
- Customizable design
- Easily extendable
Based on the market analysis and existing technical blogs, we have shortlisted four blogging platforms:
Let's see in detail our choice:
Medium is one of the best blogging platform with a vast community but not available in every country.
Pro and cons:
- User-friendly interface and editing features
- Large user base
- Saas platform
- Paid subscription needed for readers
- Blocked in China mainland
Is becoming the best place to share and exchange technical articles around development topics.
Pro and cons:
- User-friendly interface
- Free for everyone
- Saas platform
- Focused on technical and development oriented articles.
- Young platform with small user base.
It is the most widely used content management system (CMS) to create any website (landing page, blog, corporate site, ...). Its primary disadvantage is that it meets all needs without targeting one in particular.
Pro and cons:
- Very important technical community to adapt wordpress.
- Extremely extendable in terms of design and functionalities.
- Open source tool.
- Saas or Self Hosted
- Many plugins to validate/use before having something usable.
- Too many functionalities for our needs.
Ghost is a powerful app for blog creators to publish, share, and grow a business around their content. It comes with modern tools to publish content.
Pro and cons:
- User-friendly interface, UX is top notch
- Open source tool.
- Focused as only a blogging platform.
- Saas or Self Hosted
- Not fully cloud ready.
Finally, we decided to retain Ghost because this tool is made to meet the needs of a blog, and important actors use it in IT like Mozilla, Revolut, Cloudflare, OpenAI, Apple, etc...
In parallel with the sourcing of the blogging platform, we defined an editorial guideline and a process with the Michelin communication Team to publish articles.
By editorial guidelines, we mean defining our playground, such as the tone to be used, the language of articles, the topics to be covered, the profiles of contributors...
Then we worked on defining the identity of our blog.
For the graphical part, the blog had to respect the corporate identity and style guide (colors, fonts, ...). Our efforts were therefore concentrated on finding a slogan to carry this identity that we wanted a little provocative while being clear and punchy.
And we found it :
Ready to dust off the image of IT in the industry?
First, we decided to use Ghost as a SaaS service for the first year by subscribing to Ghost Pro to use the service quickly.
Ghost pro offers several price tiers for a managed service with patches, backups, CDN, SSL certificate management, with limitations on the number of users and administrators.
After that, we created a Michelin theme based on the default Ghost theme (Casper). We updated the font and color of the default theme to respect the Michelin Group's visual identity and extended it to integrate some libraries.
Two libraries have been added to the default theme:
- tarteaucitron.js to easily respect the privacy of your visitors and ask for their consent on third-party services and cookies.
- medium-zoom.js to zoom on the images of a post.
After one year of using the blog in SaaS with Ghost Pro, we encountered one issue with the license. The number of people who could publish a post was limited to 15 users (staff users) in the largest license without paying an extra 5€/month/user.
So we decide to host and deploy the blog in Michelin infrastructure.
Recently the number of staff users changed to unlimited in the Ghost Pro Business license.
Azure Paas Services
The principal requirement of Ghost is to use a Mysql database. So we created a dedicated "Azure database for Mysql" with an Azure private endpoint to allow connections to the database only from it and block all other access.
By default, Ghost stores any asset uploaded locally to its filesystem and delivers them via the same Ghost front-end service which delivers web pages. To improve performance for assets loading and decrease server load we use a custom storage adapter to manage assets directly through Azure blob storage service.
We set up our Azure blob storage to allow public blob access to be able to retrieve images from internet and we deploy Azure Front Door on top of the blob storage to minimize latency. Azure Front Door is a content delivery network (CDN) service that delivers high performance and scalability for our content. This CDN allows us to serve worldwide assets with a custom DNS Anycast managed by Microsoft.
These Azure services are deployed in the same Azure resource group dedicated to our blog production environment.
To avoid maintaining a virtual Machine dedicated to hosting the blog, we deployed Ghost in Kubernetes using the community docker image.
Based on this Docker Image we extended it by adding the custom storage adapter and the Michelin theme.
FROM docker.michelin.com/ghost:4.xx.x-alpine # Add storage adapter RUN mkdir -p content/adapters/storage COPY ghost-azure-storage/ content/adapters/storage/ghost-azure-storage/ # Add Michelin theme RUN mkdir -p content/themes/Michelin ADD Michelin.zip content/themes/ RUN unzip content/themes/Michelin.zip -d content/themes/Michelin
To deploy the application into Kubernetes we used a Kustomize template.
apiVersion: apps/v1 kind: Deployment metadata: name: ghost labels: app.kubernetes.io/name: ghost app.kubernetes.io/managed-by: kustomize spec: selector: matchLabels: app.kubernetes.io/name: ghost replicas: 1 strategy: type: RollingUpdate template: metadata: labels: app.kubernetes.io/name: ghost spec: containers: - name: ghost image: ghost-image ports: - name: https containerPort: 2368 resources: limits: cpu: 1000m memory: 1Gi requests: cpu: 350m memory: 512Mi
For this deployment, only one replica was needed as Ghost doesn't currently support High Availability. So it is not possible today to deploy multiple instances of Ghost behind a load-balancer without issues (cf documentation).
Ghost does not consume much in terms of resources knowing that the images are served with a CDN.
We have here 1 CPU and 1GiB of RAM in limit.
Deployment container configuration:
apiVersion: apps/v1 kind: Deployment metadata: name: ghost spec: template: spec: containers: - name: ghost volumeMounts: - name: config mountPath: /var/lib/ghost/config.production.json subPath: config.production.json readOnly: true readinessProbe: httpGet: path: /ghost/api/v4/admin/site/ port: https httpHeaders: - name: X-Forwarded-Proto value: https - name: Host value: blogit.michelin.io initialDelaySeconds: 30 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 6 successThreshold: 1 livenessProbe: httpGet: path: /ghost/api/v4/admin/site/ port: https httpHeaders: - name: X-Forwarded-Proto value: https - name: Host value: blogit.michelin.io initialDelaySeconds: 120 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 6 successThreshold: 1 volumes: - name: config secret: secretName: config
We use and declare a volume 'config' managed as a secret in kubernetes based on a specific json configuration file to configure the service.
Kubernetes also needs to monitor the state of the service (liveness and readiness), for that it needs API monitoring endpoints. By default, ghost don't provide health API endpoints, so we used the admin API to monitor the application state as a workaround.
For the probe, the
initialDelaySeconds is more important on liveness than readiness because some database schema migrations can take time.
Continuous integration & deployment
Finally, to ease deployment and upgrade we implemented a custom CI/CD with Gitlab to build, archive, promote and deploy our blogging platform.
Steps details :
- build : Construct the Azure Blob Storage Adapter with NPM
- test (Only for Merge Request): launch a dry run on Kustomize template and create the docker image locally with Kaniko.
- archive : build and push the docker image to our docker registry with Kaniko
- promote : flag the constructed image as ready to be deployed in production
- deploy : apply Kustomize template in the production cluster to update the desired application state.
Now, for each update or each code changes in the Git repository, a pipeline is triggered for Merge Request, we build and test containers. On the main branch we build, archive and deploy to acceptance environment and on a specific git tag the above pipeline deploys to production.
Hosting the Ghost platform ourselves leveraging Azure PaaS services and Michelin internal services allows us to put in place the "eat your own dog food" practice for this blogging platform.
Today's only downside to deploying Ghost in a container is that Ghost v4 is stateful. By default, Ghost caches all created blog posts, pages on the file system to improve performance. But this prevents us from having multiple blog engine instances to perform high availability.
To conclude, I love the simplicity of the ghost platform, it is precisely what we needed. Something simple, powerful with a large community.
Ghost also natively offers integration with some popular services like Zapier, Google Analytics, AMP, ... to extend the current platform's capabilities.
For example, we integrated Ghost and our Twitter account @michelinIT_eng to notify our community of new blog posts. A great first step to reinforce our external visibility.