A local mirrored yum repository is a copy of the standard yum repositories for CentOS that exists on your local network. It is kept up to date by synchronizing to an external site. If you have more than a few local hosts, using a local mirrored repo can significantly reduce internet traffic and speed up the update process.
This blog describes how to create, sync, test and use a local mirrored repo for CentOS 6.4. You will be shown how to create the repo, keep it in sync using cron, test the implementation and how to update the /etc/yum.repos.d directory on each host. It also provides 4 bash scripts that you can customize for your site to make maintenance easier.
Contents
- Introduction
- Site Configuration Parameters
- Configuration and Synchronization Scripts
- Initialize Your Mirrored Repository
- Test Your Mirrored Repository
- Cron the Synchronization
- Use Your Mirrored Repository
Introduction
When you type “yum update” on a standard CentOS installation your site is updated from an external site that is randomly chosen from a list of mirrored repositories on the internet. The packages that need to be updated are detected by comparing version information from your site with the version information from the external site and then downloaded through the internet.
For a small number of hosts this works really well because most packages are small and there normally only a few that are updated at a time but once your network grows to more that a small number of hosts, keeping them all in sync can result in a fair amount of internet traffic.
Please note that the term “a small number of hosts” is deliberately vague. Some sites with a slow internet connection would probably see significant performance improvements once they reached 3 or 4 hosts whereas others with a very fast, fat pipe might not see any improvement until they had dozens of hosts. In my case I decided to switch over when I had about 20.
To reduce the internet traffic overhead associated with updating multiple hosts on your network, you can create a local mirrored repository that acts just like an external repository (which is, itself, mirrored). If you have a large site with a fast, fat internet pipe you should consider becoming an external repository to help the community.
This blog describes how to create the local repository, how to keep it synchronized, how to test it and how to update the /etc/yum.repos.d directories on your hosts to make it all work.
Site Configuration Parameters
The first thing that you need to do is determine where you want the local repository to reside. It must be a network resource that is visible to all of the hosts that want to use it. I use an NFS mount. For the purposes of this discussion we will assume that the network path to the mirrored repository is “/shared/repo/
“.
Second you need to choose an administrative host that can create/update the repo directories and run the cron job to keep it in sync. For this discussion we will use the host “bigbob
“.
Finally you need to determine which repositories you want to mirror. That decision is going to be site dependent but for me I chose the centos_6.4_extras, centos_6.4_os, centos_6.4_updates and elrepo_el6 repositories. The repositories with the “centos_” prefix. The repository with the “elrepo_el6” prefix is from the elrepo repository. It contains lots of useful stuff that is not in the standard release.
To see the repositories that you reference by default, take a look at the “
/etc/yum.repos.d/*repo
files on your system.
Here is a summary of the site configuration:
Network Path /shared/repo
Admin Host bigbob
Admin Path /shared/repo/bin
CentOS 5.4 Repos extras, os, updates
Other Repos elrepo
Conf Script /shared/repo/bin/repo_conf.sh
Sync Script /shared/repo/bin/repo_sync.sh
Test Script /shared/repo/bin/repo_test.sh
Update Script /shared/repo/bin/repo_update.sh
Configuration and Synchronization Scripts
To configure your site, log into bigbob (your administrative host) and make sure that you have the createrepo package installed. You can do this by running the “yum info createrepo
” to see if it is installed. If it is, then you are good to go. If it isn’t then run “yum install -y createrepo
“.
After that create the /shared/repo/bin
directory so that you have a place to store the scripts and make sure the /shared/repo
is available as an NFS mount (or automount). I am not going to describe how to create an NFS mount or automount for hosts on your site. It is a complex topic and there are many great resources available on the web.
repo_conf.sh
Now you need to create the site configuration file. This is a simple bash script that is used by the other scripts to get basic configuration information such as where the repo is located.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#!/bin/bash # # Keep my local CentOS 6.4 repo mirror up to date. The local repo # mirror is used by all CentOS 6.4 hosts on the internal nework. # mirror_repo="/shared/repo" if [ ! -d $mirror_repo ] ; then echo "ERROR: mirror repo directory does not exist: $mirror_repo" exit 1 fi # # This is the generated repo file that is used in the # /etc/yum.repos.d directory. # mirror_repo_file="$mirror_repo/yum.repos.d/local.repo" # # This is the test directory. # mirror_repo_test="$mirror_repo/test" |
repo_sync.sh
Finally you need to create the synchronization script. This script also creates the mirror.
You will need to update the list
variable at line 55 to change the external reference to an rsync site near you.
You will also need to update the excludes
variable at line 66 to define which packages you wish to exclude. By default all packages are included.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
#!/bin/bash # # Keep the local mirror repo in sync with the external repo. # This can be run in a cron jobs as follows: # # # Update once per day at 10:00PM. # 0 22 * * * /shared/repo/bin/repo_sync.sh # # The configuration information is /shared/repo/bin/repo_conf.sh. # umask 0 # # Keep my local CentOS 6.4 repo mirror up to date. The local repo # mirror is used by all CentOS 6.4 hosts on the internal nework. # medir=$(dirname -- $(readlink -f $0)) if [ -f "$medir/repo_conf.sh" ] ; then . $medir/repo_conf.sh else echo "ERROR: missing file: $medir/repo_conf.sh." exit 1 fi if [[ "$mirror_repo" == "" ]] ; then echo "ERROR: mirror_repo not defined." exit 1 fi if [[ "$mirror_repo_file" == "" ]] ; then echo "ERROR: mirror_repo_file not defined." exit 1 fi # Semaphore file used to avoid collisions. semaphore="$mirror_repo/.busy" if [ -f $semaphore ] ; then echo "WARNING: sync already running: $(cat $semaphore)" exit 0 fi echo "pid=$$, host=$(hostname -f), user=$(whoami), date='$(date)', sem=$semaphore" >$semaphore chmod a+rw $semaphore # Trap ^C interrupt. function keyboard_interrupt() { echo echo "^C interrupt, exiting..." rm -f $semaphore exit 1 } trap keyboard_interrupt SIGINT # Emulate a 2D array. # The first entry is the source, the second is the dst. # Note that the trailing backslashes are important to rsync. list=( # First pair. "rsync://mirrors.cat.pdx.edu/centos/6.4/" "${mirror_repo}/centos/6.4/" # Second pair. # URL: http://elrepo.org/tiki/Download ##! "rsync://mirrors.thzhost.com/elrepo/elrepo/el6/x86_64/" 'rsync://mirrors.neterra.net/elrepo/elrepo/el6/x86_64/' "${mirror_repo}/elrepo/el6/x86_64/" ) # Packages to exclude. excludes=( 'local_centos_6.4_xen4_x86_64' 'local_centos_6.4_centosplus_x86_64' 'local_centos_6.4_fasttrack_x86_64' ) # Iterate over the pairs and rsync. # Note the bandwidth is limited to be a good citizen. # Also note that there is some trickiness where I used the --filter # option because the equivalent --exclude command didn't work. len=$(( ${#list[@]} / 2 - 1 )) for i in $(seq 0 $len) ; do srcidx=$(( $i * 2 )) src=${list[$srcidx]} dstidx=$(( $srcidx + 1 )) dst=${list[$dstidx]} echo echo '# ================================================================' echo '# rsync' echo "# src: $src" echo "# dst: $dst" echo '# ================================================================' if [ ! -d $dst ] ; then mkdir -p $dst fi rsync -avzP \ --delete \ --bwlimit=1024 \ --prune-empty-dirs \ --include='*.rpm' \ --exclude='i386' \ --exclude='repodata' \ --filter='-! */' \ $src $dst done # Now create the yum repo data. mirrored_pkgs=$(find $mirror_repo -type d -name x86_64) for mirrored_pkg in ${mirrored_pkgs[@]}; do echo echo '# ================================================================' echo "# createrepo --update $mirrored_pkg" echo '# ================================================================' createrepo --pretty --workers 4 --update $mirrored_pkg done echo echo '# ================================================================' echo "# creating $mirror_repo/yum.repos.d/local.repo" echo '# ================================================================' local_repo="$mirror_repo_file" local_repo_dir=$(dirname -- $local_repo) if [ ! -d $local_repo_dir ] ; then mkdir -p $local_repo_dir fi if [ -f $local_repo ] ; then rm -f ${local_repo} fi offset=$(( ${#mirror_repo} + 2)) i=0 for mirrored_pkg in ${mirrored_pkgs[@]}; do #echo $mirrored_pkg title=$(echo $mirrored_pkg | cut -c ${offset}- | tr '/' '_') if (( $i > 0 )) ; then echo >>${local_repo} fi title="local_$title" prefix='' for exclude in ${excludes[@]}; do if [[ "$exclude" == "$title" ]] ; then prefix='##!' fi done cat >>${local_repo} <<EOF ${prefix}[$title] ${prefix}name=$title ${prefix}baseurl=file://$mirrored_pkg ${prefix}gpgcheck=0 ${prefix}enabled=1 EOF i=$(( $i + 1 )) done echo echo '# ================================================================' echo "# available repos" echo '# ================================================================' for mirrored_pkg in ${mirrored_pkgs[@]}; do echo $mirrored_pkg done echo echo "local_repo_file: $local_repo" rm -f $semaphore echo echo "repo_sync done" |
Initialize Your Mirrored Repository
Run the /shared/repo/bin/repo_sync.sh
script to create the local mirrored repository. When this script finishes the following top level directories will have been created.
/shared/repo/centos
The CentOS 6.4 repositories. /shared/repo/elrepo
The elrepo repositories. /shared/repo/yum.repos.d
The local.repo configuration for each host.
At this point the mirrored repository only exists on disk. It is not used.
Test Your Mirrored Repository
Before putting the mirrored repository into production we need to test it. That is done by running the following test script to create dummy local installation and then running the “yum list
” command.
It works by creating /shared/repo/test
directory that contains a local yum configuration file and then running the yum list command with that information: “yum -c yum.conf list
“.
repo_test.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
#!/bin/bash # # Test the installation by creating a local # yum configuration. # medir=$(dirname -- $(readlink -f $0)) if [ -f "$medir/repo_conf.sh" ] ; then . $medir/repo_conf.sh else echo "ERROR: missing file: $medir/repo_conf.sh." exit 1 fi if [[ "$mirror_repo_test" == "" ]] ; then echo "ERROR: mirror_repo_test not defined." exit 1 fi if [[ "$mirror_repo_file" == "" ]] ; then echo "ERROR: mirror_repo_file not defined." exit 1 fi if [ ! -f $mirror_repo_file ] ; then echo "ERROR: local repo file not found: $mirror_repo_file" exit 1 fi cd $mirror_repo_test test_path=$(readlink -f .) echo $test_path if [ ! -f "yum.conf" ] ; then cat >yum.conf <<EOF [main] reposdir=${test_path}/yum.repos.d persistdir=${test_path}/var/lib/yum cachedir=${test_path}/var/cache/yum/$basearch/$releasever keepcache=0 debuglevel=2 logfile=${test_path}/var/log/yum.log exactarch=1 obsoletes=1 gpgcheck=1 plugins=1 installonly_limit=5 bugtracker_url=http://bugs.centos.org/set_project.php?project_id=16&ref=http://bugs.centos.org/bug_report_page.php?category=yum distroverpkg=centos-release EOF fi if [ ! -d "yum.repos.d" ] ; then mkdir yum.repos.d fi cp $mirror_repo_file yum.repos.d/ yum -c yum.conf list |
Cron the Synchronization
The repository that you are mirroring will change periodically as bug fixes are released and packages are enhanced. When those changes occur, you need to make sure that they are reflected at your site. You do this by creating a cron job that periodically run the /shared/repo/bin/repo_sync.sh script.
0 22 * * * cd /shared/repo/bin && repo_sync.sh 2>cron.err 1>cron.out
As you can see, I do this once per day but that is probably overkill. You probably only need to do it weekly.
Please note that some sites lock down the package versions that are available to avoid the risk of instability so they would not do this.
Use Your Mirrored Repository
At this point you have a fully functioning mirrored repository but no one is using it. To use it on a host you to do the following:
- Log onto the host.
- Create a backup of the
/etc/yum.repos.d
directory. - Remove all of the files in
/etc/yum.repos.d
directory. - Copy
/shared/repo/yum.repos.d/local.repo
to/etc/yum.repos.d
repo_update.sh
You can use this script (or a variant) to do this automatically.
This script will automatically make a backup for your and it is re-entrant so it can be run it multiple times without causing problems because it will only update/backup if something has changed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
#!/bin/bash # # This script will update a host so that it uses a the new local repo. # It must be run as root. # umask 0 medir=$(dirname -- $(readlink -f $0)) if [ -f "$medir/repo_conf.sh" ] ; then . $medir/repo_conf.sh else echo "ERROR: missing file: $medir/repo_conf.sh." exit 1 fi if [[ "$mirror_repo" == "" ]] ; then echo "ERROR: mirror_repo not defined." exit 1 fi if [[ "$mirror_repo_file" == "" ]] ; then echo "ERROR: mirror_repo_file not defined." exit 1 fi # # First see if we need to do anything. # if [ -d /etc/yum.repos.d ] ; then if [ ! -w /etc/yum.repos.d ] ; then echo "ERROR: you do not have write permission for /etc/yum.repos.d" exit 1 fi fi local_repo="/etc/yum.repos.d/$(basename $mirror_repo_file)" num=$(ls -1 /etc/yum.repos.d/* | wc -l) if (( $num == 1 )) ; then # There is only one file in the depot. # It is ours? if [ -f $local_repo ] ; then # It is, copy over the new one if it is different. sum1=$(sum $mirror_repo_file | awk '{print $1}') sum2=$(sum $local_repo | awk '{print $1}') if (( $sum1 != $sum2 )) ; then cp -v $mirror_repo_file $local_repo fi # all done. exit 0 fi fi # # Backup the current version and create the new one. # if [ -d "/etc/yum.repos.d" ] ; then dts=$(date +'%Y%m%d%H%M%S') backup_dir="/etc/yum.repos.d.backup.$dts" if [ -f $backup_dir ] ; then echo "ERROR: time stamped backup dir exists: $backup_dir" exit 1 fi mv /etc/yum.repos.d $backup_dir fi mkdir /etc/yum.repos.d chmod 0755 /etc/yum.repos.d cp -v $mirror_repo_file $local_repo |
Got to be the best Sys Admin How-To article I’ve ever seen! Nicely detailed & out of this world scripting. For a scripting novice, this is an amazing example.
regarding repo_sync.sh … I changed the external references to these
list=(
# OS source & local destination.
"rsync://mirrors.loosefoot.com/centos/6.4/os/x86_64/"
"${mirror_repo}/centos/6.4/os/x86_64/"
# updates source & local destination.
"rsync://mirrors.loosefoot.com/centos/6.4/updates/x86_64/"
"${mirror_repo}/centos/6.4/updates/x86_64/"
# extras source & local destination.
"rsync://mirrors.loosefoot.com/centos/6.4/extras/x86_64/"
"${mirror_repo}/centos/6.4/extras/x86_64/"
# EPEL source & local destination.
"rsync://mirrors.kernel.org/fedora-epel/6/x86_64/"
"${mirror_repo}/elrepo/el6/x86_64/"
)
I kept everything the same. Ran the script; repos downloaded via rsync; then ran into this:
# ================================================================
# createrepo --update /shared/repo/elrepo/el6/x86_64
# ================================================================
Could not find valid repo at: /shared/repo/elrepo/el6/x86_64
Spawning worker 0 with 2997 pkgs
Spawning worker 1 with 2998 pkgs
Spawning worker 2 with 2997 pkgs
Spawning worker 3 with 2998 pkgs
At this point the script stalls.
I ^c out, an ran again. repos were up to date, so it got to this point
createrepo –update /shared/repo/elrepo/el6/x86_64
… and stalled again.
Is there a configuration I can clear so I can just run the script again & get past this problem?
Thanks!
Thank you for your kind comments.
The problem that you are running into is probably due to the fact that you do not have a shared mount point on your NFS server named “
/share
“. You can easily verify that by trying “ls /shared/*
” or by typing themount
command.Your best bet is to find out what your mount points are and then change the mirror_repo variable at line 7 in the repo_conf.sh file to reflect that.
Turns out it wasn’t stalled … just took a very long time to complete.
repo_conf.sh … line 7 … was set to this as I intended:
mirror_repo=”/shared/repo”
Changed the elrepo reference from rsync://mirrors.thzhost.com/elrepo/elrepo/el6/x86_64/ to rsync://mirrors.neterra.net/elrepo/elrepo/el6/x86_64/ around line 62 of repo_sync.sh because it was generating a protection violation.
Great post, thank you! In section: Initialize Your Mirrored Repository, you state that:
/shared/repo/elrepo = The EPEL repositories.
But elrepo is not epel.
Thank you for pointing that out. I have changed the epel references to elrepo references to correct the problem.
Wow! This is comprehensive and nearly bullet proof. I didn’t have to think about this much at all. All I did was edit the location of the repo and the version of CentOS to 6.5. The only oddity that I encountered was that some of the exclusions that I added were ignored. for instance xen4 and fasttrack and SCL” were still downloaded:
)
# Packages to exclude.
excludes=(
‘local_centos_6.5_xen4_x86_64’
‘local_centos_6.5_SCL_x86_64’
‘local_centos_6.5_contrib_x86_64’
‘local_centos_6.5_cr_x86_64’
‘local_centos_6.5_isos_x86_64’
‘local_centos_6.5_fasttrack_x86_64’
)
I can’t figure out why, but thank you very much! This saved me a lot of effort.
First of all, this is the best description on this topic I’ve ever seen.
Second, a typo:
“Finally you need to do is determine which repositories you want to mirror” ->
“Finally you need to determine which repositories you want to mirror”
Thank you for your kind comments. I have fixed the typo.