I have yet to try IBM Cloud and decided with Docker’s latest rate limiting on free accounts, it might be worth looking into alternate stores for containers. IBM Cloud has a small number of free tier options one of which is a small Container Registry.
One thing you must understand before we move forward is the difference between something like Harbor.io and ACR and the rest; ECR, GCR, IBM CR and Docker Hub. The former are infrastructure plays - that is, the container registry is yours and yours alone with a unique name. The latter is a “namespace” on a shared cluster.
This often lowers cost but one needs to be aware that you are using shared infrastructure - that a “namespace” is just an isolated prefix (like a Github path) and not an instance (like an isolated website).
Create an Account
First we need to create an IBM Cloud account if we haven’t already.
Click next on through to create
Once created, login to IBM Cloud
Create a CR
Let’s create a CR in the Free tier..
The free tier includes 500mb of storage and 5gb of pull. But the calculator still shows costs.
Using the Registry
We can follow the guide here.
In WSL, we will install the IBM Cloud CLI and the CR Plugin. All things in IBM Cloud CLI are added in as plugins.
builder@DESKTOP-2SQ9NQM:~$ curl -fsSL https://clis.cloud.ibm.com/install/linux | sh
Current platform is linux64. Downloading corresponding IBM Cloud CLI...
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 111 100 111 0 0 466 0 --:--:-- --:--:-- --:--:-- 466
100 13.9M 100 13.9M 0 0 5882k 0 0:00:02 0:00:02 --:--:-- 8558k
Download complete. Executing installer...
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 40 100 40 0 0 223 0 --:--:-- --:--:-- --:--:-- 224
Bluemix_CLI/
Bluemix_CLI/bin/
Bluemix_CLI/bin/ibmcloud
Bluemix_CLI/bin/ibmcloud.sig
Bluemix_CLI/bin/ibmcloud-analytics
Bluemix_CLI/bin/ibmcloud-analytics.sig
Bluemix_CLI/bin/NOTICE
Bluemix_CLI/bin/LICENSE
Bluemix_CLI/bin/CF_CLI_Notices.txt
Bluemix_CLI/bin/CF_CLI_SLC_Notices.txt
Bluemix_CLI/autocomplete/
Bluemix_CLI/autocomplete/bash_autocomplete
Bluemix_CLI/autocomplete/zsh_autocomplete
Bluemix_CLI/install
Bluemix_CLI/uninstall
Bluemix_CLI/install_bluemix_cli
[sudo] password for builder:
Install complete.
Install the container registry plugin:
$ ibmcloud plugin install container-registry -r 'IBM Cloud'
Looking up 'container-registry' from repository 'IBM Cloud'...
Plug-in 'container-registry 0.1.497' found in repository 'IBM Cloud'
Attempting to download the binary file...
28.56 MiB / 28.56 MiB [====================================================================] 100.00% 1s
29945856 bytes downloaded
Installing binary...
OK
Plug-in 'container-registry 0.1.497' was successfully installed into /home/builder/.bluemix/plugins/container-registry. Use 'ibmcloud plugin show container-registry' to show its details.
Now login into IBM Cloud
$ ibmcloud login -a https://cloud.ibm.com
API endpoint: https://cloud.ibm.com
Email> isaac.johnson@gmail.com
Password>
Authenticating...
OK
Targeted account Isaac Johnson's Account (218912341234123412341234123478a0b)
Select a region (or press enter to skip):
1. au-syd
2. in-che
3. jp-osa
4. jp-tok
5. kr-seo
6. eu-de
7. eu-gb
8. ca-tor
9. us-south
10. us-east
Enter a number> 9
Targeted region us-south
API endpoint: https://cloud.ibm.com
Region: us-south
User: isaac.johnson@gmail.com
Account: Isaac Johnson's Account (218912341234123412341234123478a0b)
Resource group: No resource group targeted, use 'ibmcloud target -g RESOURCE_GROUP'
CF API endpoint:
Org:
Space:
We'd like to collect usage statistics to help improve the IBM Cloud CLI.
This data will never be shared outside IBM.
To learn more, see the IBM Privacy Policy: https://www.ibm.com/privacy
You can enable or disable usage data collection by running 'ibmcloud config --usage-stats-collect [true | false]'
Do you want to send usage statistics to IBM? [y/n]> n
Verify we have us-south set (we should).
$ ibmcloud cr region-set us-south
The region is set to 'us-south', the registry is 'us.icr.io'.
OK
So now we add a namespace in the registry (as opposed to a brand new registry)
$ ibmcloud cr namespace-add freshbrewedcr
No resource group is targeted. Therefore, the default resource group for the account ('Default') is targeted.
Adding namespace 'freshbrewedcr' in resource group 'Default' for account Isaac Johnson's Account in registry us.icr.io...
Successfully added namespace 'freshbrewedcr'
OK
We now need to create an API KEY to use later for login.
$ ibmcloud iam api-key-create MyKey -d "this is my API key" --file ibmAPIKeyFile
Creating API key MyKey under 218912341234123412341238a0b as isaac.johnson@gmail.com...
OK
API key MyKey was created
Successfully save API key information to ibmAPIKeyFile
builder@DESKTOP-2SQ9NQM:~$ cat ibmAPIKeyFile
{
"id": "ApiKey-27a882c5-fe12-4dbf-8a03-17fe3eea8171",
"crn": "crn:v1:bluemix:public:iam-identity::a/2189123123123123123123123bF::apikey:ApiKey-27a81233-1231-1233-1233-12333eea8171",
"iam_id": "IBMid-550009Q6G9",
"account_id": "218912312312312312312378a0b",
"name": "MyKey",
"description": "this is my API key",
"apikey": "K2xL71231231231231231212321312gl-r",
"locked": false,
"entity_tag": "1-4512312312312312312312312df",
"created_at": "2020-11-20T01:28+0000",
"created_by": "IBMid-550009Q6G9",
"modified_at": "2020-11-20T01:28+0000"
}builder@DESKTOP-2SQ9NQM:~$
Then we create a new principal, an IBM Service ID
builder@DESKTOP-2SQ9NQM:~$ ibmcloud iam service-id-create localk3s-default-id --description "Service ID for IBM Cloud Container Registry in Kubernetes Cluster k3s namesace default"
Creating service ID localk3s-default-id bound to current account as isaac.johnson@gmail.com...
OK
Service ID localk3s-default-id is created successfully
ID ServiceId-asdfasdf-123123-123123-1232131-123123123bab8a
Name localk3s-default-id
Description Service ID for IBM Cloud Container Registry in Kubernetes Cluster k3s namespace default
CRN crn:v1:bluemix:public:iam-identity::a/218123121231231238a0b::serviceid:ServiceId-123123123-123123-123123-123123-23123123
Version 1-1039123123121231320548
Locked false
Bind that ID to a Manager role via a policy
builder@DESKTOP-2SQ9NQM:~$ ibmcloud iam service-policy-create localk3s-default-id --roles Manager
Creating policy under current account for service ID localk3s-default-id as isaac.johnson@gmail.com...
OK
Service policy is successfully created
Policy ID: fa1e344f-1234-1234-1234-e625d96a4eac
Version: 1-818312312312312312312bc4ed79
Roles: Manager
Resources:
Service Type All resources in account
An lastly, for that IBM Service ID we created, we want an API Key to go with it
builder@DESKTOP-2SQ9NQM:~$ ibmcloud iam service-api-key-create localk3s-default-key localk3s-default-id --description 'API key for service ID localk3s-default-id is Kubernetes cluster localk3s namespace default'
Creating API key localk3s-default-key of service ID localk3s-default-id under account 2189dasdfasdfasdfsadfsadf8a0b as isaac.johnson@gmail.com...
OK
Service ID API key localk3s-default-key is created
Please preserve the API key! It cannot be retrieved after it's created.
ID ApiKey-asdfsadfsad-asdfd-asdf-asdf-e0eeb9333714
Name localk3s-default-key
Description API key for service ID localk3s-default-id is Kubernetes cluster localk3s namespace default
Created At 2020-11-20T02:32+0000
API Key cyM2asdfasdfsadfasdfsafasdfog8C
Locked false
Using the CR
Login to your K8s cluster (using K3s here)
builder@DESKTOP-2SQ9NQM:~$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
isaac-macbookair Ready <none> 10h v1.19.3+k3s3
isaac-macbookpro Ready master 4d3h v1.19.3+k3s3
Create a CR Secret.. We will be referencing the API Key we created above (cyM2asdfasdfsadfasdfsafasdfog8C). The user will be "iamapikey" and the email should match your IBM login.
builder@DESKTOP-2SQ9NQM:~$ kubectl create secret docker-registry ibmregcred --docker-server=us.icr.io --docker-username=iamapikey --docker-password=cyM2asdfasdfsadfasdfsafasdfog8C --docker-email=isaac.johnson@gmail.com
secret/ibmregcred created
We can also login to the IBM CR locally:
builder@DESKTOP-2SQ9NQM:~$ ibmcloud cr login
Logging in to 'registry.ng.bluemix.net'...
Logged in to 'registry.ng.bluemix.net'.
Logging in to 'us.icr.io'...
Logged in to 'us.icr.io'.
OK
Pushing a Hello World Example
$ docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
Digest: sha256:e7c70bb24b462baa86c102610182e3efcb12a04854e8c582838d92970a09f323
Status: Image is up to date for hello-world:latest
docker.io/library/hello-world:latest
then we can tag and push.
builder@DESKTOP-JBA79RT:~$ docker images | grep hello
hello-world latest bf756fb1ae65 10 months ago 13.3kB
builder@DESKTOP-JBA79RT:~$ docker tag hello-world:latest us.icr.io/freshbrewedcr/hw_repo:1
builder@DESKTOP-JBA79RT:~$ docker push us.icr.io/freshbrewedcr/hw_repo:1
The push refers to repository [us.icr.io/freshbrewedcr/hw_repo]
9c27e219663c: Layer already exists
1: digest: sha256:90659bf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cbc042 size: 525
Create the k8s deployment YAML that will use this and apply
builder@DESKTOP-2SQ9NQM:~$ cat ibm_image.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: helloworld-deployment
labels:
app: helloworld
spec:
replicas: 1
selector:
matchLabels:
app: helloworld
template:
metadata:
labels:
app: helloworld
spec:
containers:
- name: helloworld
image: us.icr.io/freshbrewedcr/hw_repo:1
ports:
- containerPort: 80
imagePullSecrets:
- name: ibmregcred
builder@DESKTOP-2SQ9NQM:~$ kubectl apply -f ibm_image.yaml
deployment.apps/helloworld-deployment created
Verify it ran (hello-world doesn't expose a service so it will crash)
builder@DESKTOP-2SQ9NQM:~$ kubectl describe pod helloworld-deployment-7f5cff7cf8-7qgms | tail -n10
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled <unknown> Successfully assigned default/helloworld-deployment-7f5cff7cf8-7qgms to isaac-macbookair
Normal Pulling 73s kubelet, isaac-macbookair Pulling image "us.icr.io/freshbrewedcr/hw_repo:1"
Normal Pulled 68s kubelet, isaac-macbookair Successfully pulled image "us.icr.io/freshbrewedcr/hw_repo:1" in 5.496330884s
Normal Pulled 20s (x3 over 67s) kubelet, isaac-macbookair Container image "us.icr.io/freshbrewedcr/hw_repo:1" already present on machine
Normal Created 20s (x4 over 68s) kubelet, isaac-macbookair Created container helloworld
Normal Started 20s (x4 over 67s) kubelet, isaac-macbookair Started container helloworld
Warning BackOff 20s (x5 over 66s) kubelet, isaac-macbookair Back-off restarting failed container
builder@DESKTOP-2SQ9NQM:~$
builder@DESKTOP-2SQ9NQM:~$ kubectl logs helloworld-deployment-7f5cff7cf8-7qgms
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
We can also see the entry in IBM Cloud's Registry
While namespaces show our images collapsed under our namespaces, Repositories lets use look at our specific image repos
We can look at specific images in the images tab
And from there get image details
Security Checks
A nice feature for this free offering I haven't seen elsewhere (for free) is built in security scanning. Here I pushed an image that was flagged:
If we click on the issue we can see details
Be aware that this was found while writing this blog entry (2020-11-18 is the date for this CVE). So it proves it is active scanning existing images.
Our settings show us how much of our quota we have used.
Azure DevOps Integration
Let's add our CR to Azure DevOps to be used in our CICD. First create new Docker Registry Service Connection in a project.
Set the user and password as the key
Then update the pipelines to use it:
variables:
# Container registry service connection established during pipeline creation
dockerRegistryServiceConnection: '7db7c6a9-f749-4289-a33e-f7204dd7bfe2'
imageRepository: 'freshbrewedcr/hello'
containerRegistry: 'us.icr.io'
dockerfilePath: '**/Dockerfile'
tag: '$(Build.BuildId)'
imagePullSecret: 'ijk8senv5cr72877afc-auth'
Note: we need to use our “repository” in the imageRepository path (so set it as $REPO/$IMAGE)
Run a build and verify it pushed:
And we can see it in IBM Cloud
Summary
IBM CR is a small but nice offering. At 500Mb it really lends itself to small images. However, there is nothing wrong with a small free offering. I never look a gift horse in the mouth. I will be looking into Harbor.io next and perhaps using something like IBM Cloud's CR would pair nice for permanent storage of "released" images.
An aside: In a large section I didn't include, but spent several days on, I attempted to setup k3s on a Google Cloud F1 (free tier) instance with the hopes of showing a great free+free option (following this guide). No matter how I tried (and I tried everything), the tiny f1-micro class (1ecu, 640mb ram) just couldn't hold up to run k3s master node. So I scratched the whole bit. However, I will continue to search for a good free'ish container host one could pair with this CR.