Looking forward to 2020

2019 was a busy year for me. I presented sessions in over 10 countries, averaging speaking at two conferences per month. I also helped organise Data Relay and attended all five days which is always a fun (yet tiring) experience.

All in all, I thoroughly enjoyed myself last year and I hope 2020 brings more of the same! I do intend on not speaking at as many conferences this year although to be honest I did say that at the start of 2019.

I do want to get back into blogging more often this year. I didn’t publish as often as I wanted to last year and the simple reason for that was….time. So this year I plan on getting some cool stuff out there.

I’m currently writing this in Dublin airport waiting to head over to London to present my Chaos Engineering for SQL Server session at Conf42.com. I’ve been diving into the world of Chaos Engineering recently so hope to get a few posts out about it in the next few months.

I’ll also be continuing to explore running SQL Server in Docker containers and on Kubernetes. There’s so much great stuff happening in those areas that it can be difficult to keep up at times! The two main things I’m looking forward to are running SQL Server Edge on a Raspberry Pi (if they ever give me access to the container image) and WSL 2 going GA.

Another thing I’ve been enjoying is delving into the world of Linux. I’ve been using Ubuntu as my primary OS for a while now but had to refresh my laptop over Christmas in order to install Windows 10 as it’s needed for an upcoming project that I’m working on (can’t say too much about that at the moment but it’s quite exciting!). So I installed Windows 10 and then decided to install Fedora 31 instead of re-installing Ubuntu.

I can’t really say why I went for Fedora. There’s a tonne of distros out there (I seriously looked at Manjaro) but I guess the main reason was that Microsoft has two linux SQL Server 2019 container images, one for Ubuntu and one for Red Hat. Plus I suffer from Shiny Tech Syndrome so wanted to try something new.

I’ve also recently purchased a Pinebook Pro. I needed a backup laptop for when I travel (at least that’s what I’m telling myself) so nabbed one. The price can’t be argued with ($200) and the reviews online are all very positive so am looking forward to getting my hands on it. One of the really cool things is that it will boot from a SD card so I’ll get to try out Manjaro after all! 🙂 I’ll definitely be posting a review about it once I’ve had it for a couple of weeks (in fact that might get wrapped up into a larger post about either my linux experiences so far or my current travel kit…haven’t decided yet).

Finally to mention, I’ve also started helping organise a brand new conference in Ireland, Data Céilí. This conference is run by the team behind SQL Saturday Dublin and the response we’ve had so far as been amazing. We’ll be selecting sessions in the next couple of weeks and then prompting the heck out of it!

I reckon that should be enough projects to keep me occupied for the foreseeable future.

Thanks for reading and I hope 2020 is a blast.

Running SQL Server containers as non-root

Recently I noticed that Microsoft uploaded a new dockerfile to the mssql-docker repository on Github. This dockerfile was under the mssql-server-linux-non-root directory and (you guessed it) allows SQL Server containers to run as non-root.

But why is running a container as root bad? Let’s run through an example.

Using a non-root user: –

Run a SQL Server 2019 container with /etc mounted:-

docker run -d -p 15789:1433 \
--volume /etc:/etc \
--env SA_PASSWORD=Testing1122 \
--env ACCEPT_EULA=Y \
--name testcontainer \
mcr.microsoft.com/mssql/server:2019-RC1-ubuntu

Have a look at the logs: –

docker logs testcontainer

So even though I ran the container as a non-root user, the container is running as root.

Here’s the reason that’s bad. Exec into the container: –

docker exec -it testcontainer bash

Now create a user and add to the super user’s group: –

useradd testuser
passwd testuser
adduser testuser sudo

The user has been created and added to the super user’s group within the container. But if we come out of the container and run: –

cat /etc/group | grep sudo

The user is in the super user group on the host! Which means we can do: –

su testuser

Because we mounted the /etc directory into the container, the user created in the container is also created on the host!

And that’s why running containers as root is bad.


EDIT: November 2019

The new SQL Server 2019 run as a non-root user by default, these images are: –

mcr.microsoft.com/mssql/server:2019-GA-ubuntu-16.04
mcr.microsoft.com/mssql/server:2019-GDR1-ubuntu-16.04

So there’s no need to build your own image but the process below will show you how to (if you want to see how it’s done).


Let’s fix this by running SQL Server 2019 in a non-root container. First thing to do is create a mssql user on the host (you’ll have to run this as a user with sudo rights): –

useradd -M -s /bin/bash -u 10001 -g 0 mssql

N.B. – this user is needed as it’s created in the dockerfile, without it on the host the build will complete but any containers created from the image will crash.

Now, build the image from the dockerfile on Github: –

docker build -t 2019-nonroot .

Let’s try to run this container with /etc mounted: –

docker run -d -p 15799:1433 \
--volume /etc:/etc \
--env SA_PASSWORD=Testing1122 \
--env ACCEPT_EULA=Y \
--name testcontainer2 \
2019-nonroot

We can see that the container is running as the user mssql and it’s errored out as it does not have access to the /etc directory that we tried to mount!

So now that we have the option to run SQL Server in containers as a non-root user, I would absolutely recommend that you do so.

Thanks for reading!

Setting up SQL Server replication in containers

Last week I saw a thread on twitter about how to get replication setup for SQL Server running in a container. Now I know very little about replication, it’s not an area of SQL that I’ve had a lot of exposure to but I’m always up for figuring stuff out (especially when it comes to SQL in containers).

So let’s run through how to set it up here.

First, create a dockerfile to build an image from the SQL Server 2019 CTP 2.2 image with the SQL Server Agent enabled: –

FROM mcr.microsoft.com/mssql/server:2019-CTP2.2-ubuntu

RUN /opt/mssql/bin/mssql-conf set sqlagent.enabled true 

CMD /opt/mssql/bin/sqlservr

Now build the image: –

docker build -t sqlreplication .

The next step is to create a custom docker network: –

docker network create repnet

Confirm the network has been created: –

docker network ls

This stage is the key, containers running on the same network can communicate with each other by container name or IP address. More information about docker networking can be found here.

Once the network is created, run two containers using the network: –

docker run -d -p 15111:1433 `
    --env ACCEPT_EULA=Y --env SA_PASSWORD=Testing1122 `
        --net repnet `
            --name container1 `
                sqlreplication 

docker run -d -p 15222:1433 `
    --env ACCEPT_EULA=Y --env SA_PASSWORD=Testing1122 `
        --net repnet `
            --name container2 `
                sqlreplication 

The last (docker) step is to create a directory within container1 for the replication data: –

docker exec -it container1 mkdir /var/opt/mssql/data/ReplData/

And that’s it docker-wise! We can now setup replication within SQL itself. The steps below are taken from the Microsoft documentation here that goes through how to setup replication for SQL on Linux (slightly modified but not by much).

So let’s run through and see it in action!

Connect to container1 and create the database, table, and insert data that we want to replicate: –

USE [master];
GO

CREATE DATABASE [Sales];
GO

USE [SALES];
GO
 
CREATE TABLE CUSTOMER([CustomerID] [int] NOT NULL, [SalesAmount] [decimal] NOT NULL);
GO
 
INSERT INTO CUSTOMER (CustomerID, SalesAmount) VALUES (1,100),(2,200),(3,300);
GO

Connect to container2 and create (just) the database to receive the replicated data: –

USE [master];
GO

CREATE DATABASE [Sales];
GO

Connect back to container1 and configure the distributor (following the MS example the publisher will also be the distributor): –

USE [master];
GO
 
DECLARE @distributor AS SYSNAME;
DECLARE @distributorlogin AS SYSNAME;
DECLARE @distributorpassword AS SYSNAME;
DECLARE @Server SYSNAME;
 
SELECT @Server = @@servername;
 
SET @distributor = @Server;
SET @distributorlogin = N'sa';
SET @distributorpassword = N'Testing1122';
 
EXEC sp_adddistributor @distributor = @distributor;
 
EXEC sp_adddistributiondb @database = N'distribution'
    ,@log_file_size = 2
    ,@deletebatchsize_xact = 5000
    ,@deletebatchsize_cmd = 2000
    ,@security_mode = 0
    ,@login = @distributorlogin
    ,@password = @distributorpassword;
GO

USE [distribution];
GO
 
DECLARE @snapshotdirectory AS NVARCHAR(500);
 
SET @snapshotdirectory = N'/var/opt/mssql/data/ReplData/';
 
IF (NOT EXISTS (SELECT * FROM sysobjects WHERE name = 'UIProperties' AND type = 'U '))
    CREATE TABLE UIProperties (id INT);
 
IF (EXISTS (SELECT * FROM::fn_listextendedproperty('SnapshotFolder', 'user', 'dbo', 'table', 'UIProperties', NULL, NULL)))
    EXEC sp_updateextendedproperty N'SnapshotFolder'
        ,@snapshotdirectory
        ,'user'
        ,dbo
        ,'table'
        ,'UIProperties'
ELSE
    EXEC sp_addextendedproperty N'SnapshotFolder'
        ,@snapshotdirectory
        ,'user'
        ,dbo
        ,'table'
        ,'UIProperties';
GO

Configure the publisher: –

USE [distribution];
GO

DECLARE @publisher AS SYSNAME;
DECLARE @distributorlogin AS SYSNAME;
DECLARE @distributorpassword AS SYSNAME;
DECLARE @Server SYSNAME;

SELECT @Server = @@servername;

SET @publisher = @Server;
SET @distributorlogin = N'sa';
SET @distributorpassword = N'Testing1122';

EXEC sp_adddistpublisher @publisher = @publisher
	,@distribution_db = N'distribution'
	,@security_mode = 0
	,@login = @distributorlogin
	,@password = @distributorpassword
	,@working_directory = N'/var/opt/mssql/data/ReplData'
	,@trusted = N'false'
	,@thirdparty_flag = 0
	,@publisher_type = N'MSSQLSERVER';
GO

Configure the publication job run: –

USE [Sales];
GO

DECLARE @replicationdb AS SYSNAME;
DECLARE @publisherlogin AS SYSNAME;
DECLARE @publisherpassword AS SYSNAME;

SET @replicationdb = N'Sales';
SET @publisherlogin = N'sa';
SET @publisherpassword = N'Testing1122';

EXEC sp_replicationdboption @dbname = N'Sales'
	,@optname = N'publish'
	,@value = N'true';

EXEC sp_addpublication @publication = N'SnapshotRepl'
	,@description = N'Snapshot publication of database ''Sales'' from Publisher ''''.'
	,@retention = 0
	,@allow_push = N'true'
	,@repl_freq = N'snapshot'
	,@status = N'active'
	,@independent_agent = N'true';

EXEC sp_addpublication_snapshot @publication = N'SnapshotRepl'
	,@frequency_type = 1
	,@frequency_interval = 1
	,@frequency_relative_interval = 1
	,@frequency_recurrence_factor = 0
	,@frequency_subday = 8
	,@frequency_subday_interval = 1
	,@active_start_time_of_day = 0
	,@active_end_time_of_day = 235959
	,@active_start_date = 0
	,@active_end_date = 0
	,@publisher_security_mode = 0
	,@publisher_login = @publisherlogin
	,@publisher_password = @publisherpassword;
GO

Create the articles: –

USE [Sales];
GO

EXEC sp_addarticle @publication = N'SnapshotRepl'
	,@article = N'customer'
	,@source_owner = N'dbo'
	,@source_object = N'customer'
	,@type = N'logbased'
	,@description = NULL
	,@creation_script = NULL
	,@pre_creation_cmd = N'drop'
	,@schema_option = 0x000000000803509D
	,@identityrangemanagementoption = N'manual'
	,@destination_table = N'customer'
	,@destination_owner = N'dbo'
	,@vertical_partition = N'false';
GO

Configure the subscription run, note the name of the subscriber (it’s the name of the second container): –

USE [Sales];
GO

DECLARE @subscriber AS SYSNAME
DECLARE @subscriber_db AS SYSNAME
DECLARE @subscriberLogin AS SYSNAME
DECLARE @subscriberPassword AS SYSNAME

SET @subscriber = N'container2'
SET @subscriber_db = N'Sales'
SET @subscriberLogin = N'sa'
SET @subscriberPassword = N'Testing1122'

EXEC sp_addsubscription @publication = N'SnapshotRepl'
	,@subscriber = @subscriber
	,@destination_db = @subscriber_db
	,@subscription_type = N'Push'
	,@sync_type = N'automatic'
	,@article = N'all'
	,@update_mode = N'read only'
	,@subscriber_type = 0;

EXEC sp_addpushsubscription_agent @publication = N'SnapshotRepl'
	,@subscriber = @subscriber
	,@subscriber_db = @subscriber_db
	,@subscriber_security_mode = 0
	,@subscriber_login = @subscriberLogin
	,@subscriber_password = @subscriberPassword
	,@frequency_type = 1
	,@frequency_interval = 0
	,@frequency_relative_interval = 0
	,@frequency_recurrence_factor = 0
	,@frequency_subday = 0
	,@frequency_subday_interval = 0
	,@active_start_time_of_day = 0
	,@active_end_time_of_day = 0
	,@active_start_date = 0
	,@active_end_date = 19950101;
GO

Cool! Now we can run the Agent jobs: –

USE [msdb]; 
GO

DECLARE @job1 SYSNAME;

SELECT @job1 = name FROM msdb.dbo.sysjobs
WHERE name LIKE '%-Sales-SnapshotRepl-1'

EXEC dbo.sp_start_job @job1
GO


USE [msdb];
GO

DECLARE @job2 SYSNAME;

SELECT @job2 = name FROM msdb.dbo.sysjobs
WHERE name LIKE '%-Sales-SnapshotRepl-CONTAINER2-1'
 
EXEC dbo.sp_start_job @job2
GO

Awesome stuff, let’s check the data over on container2: –

SELECT * from [Sales].[dbo].[CUSTOMER]

Great stuff! We have data being replicated from a SQL instance in one container to a SQL instance in another container 🙂

Thanks for reading!

Attaching databases via a dockerfile – UPDATE

This has been bugging me for a while but I just haven’t had a chance to get around to revisiting it…you can read the original post I wrote here

In that post I came up with a way to attach databases to SQL running in a linux container. There’s an environment variable you can use in windows containers (attach_dbs) that provides the ability to attach database data and log files to a SQL instance running within a container.

Sadly this doesn’t exist for linux containers so the what I came up with was: –

HEALTHCHECK --interval=10s  \
    CMD /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Testing11@@ \
        -Q "CREATE DATABASE [DatabaseA] ON (FILENAME = '/var/opt/sqlserver/DatabaseA.mdf'),(FILENAME = '/var/opt/sqlserver/DatabaseA_log.ldf') FOR ATTACH"

Now this works a treat. It waits ten seconds for the SQL instance to come up within the container and then runs the sqlcmd script below.

The problem with this is, it’s a bit of a hack. The HEALTHCHECK command isn’t designed to run once, it’ll carry on running every 10 seconds once the container comes up…not great.

So, what’s the better way of doing it?

I sat down this weekend and started to write scripts to run the SQL statement above. However I ran into a bit of an issue, each time I ran a script like thus: –

sleep 10s

/opt/mssql-tools/bin/sqlcmd -S . -U sa -P Testing11@@ \
-Q "CREATE DATABASE [DatabaseA] ON (FILENAME = '/var/opt/sqlserver/DatabaseA.mdf'),(FILENAME = '/var/opt/sqlserver/DatabaseA_log.ldf') FOR ATTACH"

The script would error out or I would end up with a dead container. I tried a few alternatives, for example retrying the sql statement until I got a exit code of 0, but no dice.

So what was the fix? Well I was flipping through Bob Ward’s (t) excellent Pro SQL Server on Linux one evening and there was the answer!

The trick is to create two scripts called entrypoint.sh and attach-db.sh

attach-db.sh looks like this: –

sleep 15s

/opt/mssql-tools/bin/sqlcmd -S . -U sa -P Testing1122 \
-Q "CREATE DATABASE [DatabaseA] ON (FILENAME = '/var/opt/sqlserver/DatabaseA.mdf'),(FILENAME = '/var/opt/sqlserver/DatabaseA_log.ldf') FOR ATTACH"

Pretty standard right? Wait 15 seconds and then run the CREATE DATABASE statement.

But the real trick is in the entrypoint.sh script: –

/var/opt/sqlserver/attach-db.sh & /opt/mssql/bin/sqlservr

Huh? The attach-db.sh script is called before the SQL binary??

Yep, even though it’s a bit counter-initiative (well for me anyway), what’s happening here is that the attach-db.sh script is called, starts waiting, and then SQL is spun up. Then once SQL is up and running, the CREATE DATABASE statement is executed.

Containers need a process to be running in order for them to stay up, so without the SQL binary being called after the attach-db.sh script the container will shut down. That’s the issue that I was having (amongst others).

I’ve been playing around with this and ended up with the following dockerfile: –

So the docker looks like this: –

# base this image of the SQL 2017 latest image
FROM microsoft/mssql-server-linux:latest

# make a directory within the container
RUN mkdir /var/opt/sqlserver

# copy attach-db.sh into container
COPY attach-db.sh /var/opt/sqlserver

# copy database files into container
COPY DatabaseA.mdf /var/opt/sqlserver
COPY DatabaseA_log.ldf /var/opt/sqlserver

# use the ENTRYPOINT command to execute the script and start SQL Server
ENTRYPOINT /var/opt/sqlserver/attach-db.sh & /opt/mssql/bin/sqlservr

I’ve removed the entrypoint.sh script and replaced it with ENTRYPOINT command within the dockerfile. The ENTRYPOINT command specifies what will run when the container starts up, so in this case the attach-db.sh script and then SQL Server.

Boom! Once the image is created from the dockerfile, a container can be spun up, and there will be the database!

Thanks for reading!

Taking the Linux plunge – Update

It’s been a couple of weeks now since I took the plunge and bought myself a Dell XPS 13 running Ubuntu 16.04.

And to be honest, so far so good! The machine itself is absolutely fantastic and apart from one issue now resolved, Ubuntu has been great to use. I used it to present my partitioning session at the Dublin User Group this week and all went well. Using SQL in a docker container and SQL Operations Studio, my demos worked no problem.

Have to admit, the only gripe I have is that there’s a windows key instead of a key with the Ubuntu logo 🙂

So once I had booted up the first thing I did was uninstall Chrome and Chromium. After that, I went and installed: –

Firefox– with DuckDuckGo as my homepage
Docker
SQL Operations Studio
Visual Studio Code
Powershell
Remmina
Franz
Spotify
Whatever – Evernote client for Linux
Keybase

OK, there’s a couple of obvious ones in there but maybe a couple that are new to you.

If you haven’t heard of Franz, it’s awesome. One of my pet peeves these days is the amount of comms apps that I have to have open. Slack, Google Hangouts, Teams, email etc. it’s a bit of a nightmare. Franz solves that issue by combining them all into one app, ok it’s a bit of a memory hog but that’s kinda to be expected.

Another app not everyone may be familiar with is Keybase. Keybase is a security app that provides public keys, encrypted chat, and an encrypted filesystem. The filesystem offering is why I went for it, you can install OneDrive on Linux but it’s a bit of a hassle and I’m not a particular fan of Dropbox so am glad that I have another option (even if it is ANOTHER chat app, and it doesn’t integrate with Franz 😦 )

One final thing to mention is Gitpitch. Gitpitch is a markdown presentation service that integrates with Github/Gitlab/Bitbucket. You write your presentation in markdown, push it to a repo, and the service renders your presentation online (which you can then download).

I used GitPitch for my presentation to the Dublin User Group this week and was a bit concerned at first as my markdown skills are, ahem, not great. You can see the presentation I wrote here, I’m really pleased with the results. I also really enjoyed creating that presentation, which is new for me as I hate writing powerpoint slides. Am going to spend some more time this weekend seeing what else the service can do.

So all in all, am really pleased with my new toy. If you’re thinking about switching to Linux, I highly recommend that you do, what’s the worst that could happen? 🙂

Have a good weekend!